
/* GCSx
** SCENEEDITLAYER.CPP
**
** Scene editing- tile layers
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

// Tool panel

const ToolSelect::ToolIconStruct SceneEditLayerTools::toolIcons[TOOLSELECT_NUM_TOOLS] = {
    { TOOLS_PEN, 0, 0, "Draw (place tile or pattern)" },
    { TOOLS_LINE, 20, 0, "Straight line" },
    { TOOLS_DROPPER, 40, 40, "Dropper (grab tile or pattern)" },
    { TOOLS_FILL, 80, 0, "Flood fill" },
    { TOOLS_RECT, 80, 60, "Rectangle (outline)" },
    { TOOLS_RECTFILL, 40, 0, "Rectangle (filled)" },
    { TOOLS_ELLIPSE, 40, 80, "Ellipse (outline)" },
    { TOOLS_ELLIPSEFILL, 60, 0, "Ellipse (filled)" },
    { TOOLS_SELECT, 0, 20, "Select area (rectangle)" },
    { TOOLS_SELECTELLIPSE, 20, 20, "Select area (ellipse)" },
    { TOOLS_WAND, 40, 20, "Magic wand (select area by tile)" },
    { TOOLS_SELECTITEM, 0, 120, "Select/modify sprite(s)" },
    { TOOLS_PLACEITEM, 80, 100, "Place new sprite" },
    { TOOLS_PLACESERIES, 80, 120, "Place multiple sprites" },
};

FrameWindow* SceneEditLayerTools::createWindowed() { start_func
    // Prevent duplication
    if (myFrame) {
        return myFrame;
    }

    // We remember the frame pointer even though it'll delete itself
    myFrame = new FrameWindow("Tools", FrameWindow::RESIZING_SNAP, FrameWindow::FRAMETYPE_DIALOG, this, FrameWindow::TITLEBAR_TOOL, 0);
    return myFrame;
}

Window::WindowSort SceneEditLayerTools::windowSort() const { start_func
    return WINDOWSORT_ONTOP;
}

void SceneEditLayerTools::adjustForTool() { start_func
    WCheckBox* check = dynamic_cast<WCheckBox*>(findWidget(ID_CONTIGUOUS));

    if ((*toolLPtr == TOOLS_WAND) || (*toolLPtr == TOOLS_FILL) || (*toolRPtr == TOOLS_WAND) || (*toolRPtr == TOOLS_FILL)) {
        check->enable();
    }
    else {
        check->disable();
    }
    
    paintArea->newToolSelected();
}

int SceneEditLayerTools::event(int hasFocus, const SDL_Event* event) { start_func
    if (event->type == SDL_COMMAND) {
        // Simple cases
        switch (event->user.code) {
            case VIEW_GRID:
                *gridPtr = *gridPtr ? 0 : 1;
                findWidget(ID_GRID)->load();
                paintArea->refresh();
                config->write(LAYEREDIT_GRID, *gridPtr);
                return 1;

            case TOOLS_CONTIGUOUS:
                *contiguousPtr = *contiguousPtr ? 0 : 1;
                findWidget(ID_CONTIGUOUS)->load();
                return 1;
        }

        // Choose tool
        int newToolL = 0;
        int newToolR = 0;
        WButton* toolButtonL = dynamic_cast<WButton*>(findWidget(ID_TOOLL));
        WButton* toolButtonR = dynamic_cast<WButton*>(findWidget(ID_TOOLR));
        int x, y;
        int modeSwap = 0;

        if (event->user.code == TOOLS_CHOOSE) {
            newToolL = toolSelect->run(toolButtonL->getScreenX(), toolButtonL->getScreenY() + toolButtonL->getHeight());
        }
        else if (event->user.code == TOOLS_CHOOSER) {
            newToolR = toolSelect->run(toolButtonR->getScreenX(), toolButtonR->getScreenY() + toolButtonR->getHeight());
        }
        else if (toolSelect->isTool(event->user.code)) {
            newToolL = event->user.code;
            paintArea->startToolIfMove();
        }

        if ((newToolL) || (newToolR)) {
            int targetToolL = *toolLPtr;
            int targetToolR = *toolRPtr;
            
            if (newToolL) targetToolL = newToolL;
            if (newToolR) targetToolR = newToolR;
    
            // Mode swap?
            if ((targetToolL == TOOLS_PLACEITEM) || (targetToolL == TOOLS_PLACESERIES) || (targetToolL == TOOLS_SELECTITEM)) {
                if ((targetToolR != TOOLS_PLACEITEM) && (targetToolR != TOOLS_PLACESERIES) && (targetToolR != TOOLS_SELECTITEM)) {
                    modeSwap = 1;
                }
            }
            else {
                if ((targetToolR == TOOLS_PLACEITEM) || (targetToolR == TOOLS_PLACESERIES) || (targetToolR == TOOLS_SELECTITEM)) {
                    modeSwap = 1;
                }
            }
    
            if (modeSwap) {
                swap(*toolLPtr, otherModeToolL);
                swap(*toolRPtr, otherModeToolR);
            }
    
            if (newToolL) *toolLPtr = newToolL;
            if (newToolR) *toolRPtr = newToolR;
    
            if (toolSelect->getToolIcon(*toolLPtr, x, y)) toolButtonL->changeIcon(x, y);
            if (toolSelect->getToolIcon(*toolRPtr, x, y)) toolButtonR->changeIcon(x, y);
    
            adjustForTool();
            return 1;
        }

        if ((event->user.code == TOOLS_CHOOSE) || (event->user.code == TOOLS_CHOOSER)) return 1;
    }

    return Dialog::event(hasFocus, event);
}

void SceneEditLayerTools::childModified(Window* modified) { start_func
    Dialog::childModified(modified);

    Widget* widget = dynamic_cast<Widget*>(modified);

    if (widget) {
        // If certain settings modified, tile area needs to refresh
        if (widget->getId() == ID_GRID) {
            paintArea->refresh();
        }

        // Save config settings
        config->write(LAYEREDIT_GRID, *gridPtr);
    }
}

int SceneEditLayerTools::wantsToBeDeleted() const { start_func
    return 1;
}

SceneEditLayerTools::~SceneEditLayerTools() { start_func
    delete toolSelect;
}

SceneEditLayerTools::SceneEditLayerTools(SceneEditLayer* tPaintArea, int* toolL, int* toolR, int* contiguous, int* grid) : Dialog(blankString, 1, 0, 1) { start_func
    paintArea = tPaintArea;
    toolLPtr = toolL;
    toolRPtr = toolR;
    contiguousPtr = contiguous;
    gridPtr = grid;
    
    // Other-mode tools based on currently selected tool
    if ((*toolL == TOOLS_PLACEITEM) || (*toolL == TOOLS_PLACESERIES) || (*toolL == TOOLS_SELECTITEM)) {
        otherModeToolL = TOOLS_PEN;
    }
    else {
        otherModeToolL = TOOLS_PLACESERIES;
    }
    if ((*toolR == TOOLS_PLACEITEM) || (*toolR == TOOLS_PLACESERIES) || (*toolR == TOOLS_SELECTITEM)) {
        otherModeToolR = TOOLS_DROPPER;
    }
    else {
        otherModeToolR = TOOLS_SELECTITEM;
    }

    Widget* w = NULL;
    myFrame = NULL;

    toolSelect = new ToolSelect(getIconSurface(), TOOLSELECT_NUM_TOOLS, toolIcons);

    int x = 0;
    int y = 0;
    toolSelect->getToolIcon(*toolLPtr, x, y);

    w = new WButton(ID_TOOLL, getIconSurface(), x, y, 20, 20, Dialog::BUTTON_NOTHING, TOOLS_CHOOSE);
    w->setToolTip("Change tool (left button)");
    w->addTo(this);

    x = y = 0;
    toolSelect->getToolIcon(*toolRPtr, x, y);

    w = new WButton(ID_TOOLR, getIconSurface(), x, y, 20, 20, Dialog::BUTTON_NOTHING, TOOLS_CHOOSER);
    w->setToolTip("Change tool (right button)");
    w->addTo(this);

    w = new WCheckBox(ID_CONTIGUOUS, "Contiguous", contiguous, 1);
    w->setToolTip("Only flood-fill or wand-select a single, connected area");
    w->addTo(this);

    arrangeRow();

    w = new WButton(ID_COPY, getIconSurface(), 60, 20, 20, 20, Dialog::BUTTON_NOTHING, EDIT_COPY);
    w->setToolTip("Copy");
    w->addTo(this);

    w = new WButton(ID_PASTE, getIconSurface(), 80, 20, 20, 20, Dialog::BUTTON_NOTHING, EDIT_PASTE);
    w->setToolTip("Paste (as new selection)");
    w->addTo(this);

    w = new WCheckBox(ID_GRID, getIconSurface(), 0, 100, 20, 20, grid, 1);
    w->setToolTip("Show or hide grid");
    w->addTo(this);

    arrangeRow();

    adjustForTool();
    runAsPanel();
}

// Layer panel

FrameWindow* SceneEditLayerList::createWindowed() { start_func
    // Prevent duplication
    if (myFrame) {
        return myFrame;
    }

    // We remember the frame pointer even though it'll delete itself
    myFrame = new FrameWindow("Layers", FrameWindow::RESIZING_SNAP, FrameWindow::FRAMETYPE_DIALOG, this, FrameWindow::TITLEBAR_TOOL, 0);
    return myFrame;
}

Window::WindowSort SceneEditLayerList::windowSort() const { start_func
    return WINDOWSORT_ONTOP;
}

int SceneEditLayerList::event(int hasFocus, const SDL_Event* event) { start_func
    if (event->type == SDL_COMMAND) {
        // Simple cases
        switch (event->user.code) {
            case VIEW_ALLLAYER:
            case VIEW_DIMLAYER:
            case VIEW_NOLAYER:
            case VIEW_PREV:
            case VIEW_NEXT:
                return layerList->event(0, event);
        }
    }

    return Dialog::event(hasFocus, event);
}

void SceneEditLayerList::childModified(Window* modified) { start_func
    Dialog::childModified(modified);

    Widget* widget = dynamic_cast<Widget*>(modified);

    if (widget) {
        // Layer visibility/selection
        if (widget == layerList) {
            paintArea->refreshLayers();
        }
    }
}

int SceneEditLayerList::wantsToBeDeleted() const { start_func
    return 1;
}

SceneEditLayerList::~SceneEditLayerList() { start_func
}

SceneEditLayerList::SceneEditLayerList(SceneEditLayer* tPaintArea) : Dialog(blankString, 1, 0, 1) { start_func
    paintArea = tPaintArea;

    Widget* w = NULL;
    myFrame = NULL;

    w = layerList = new WLayerListBox(ID_LAYER, &selectedLayer);
    // @TODO: More specific tooltips
    w->setToolTip("Select which layer(s) to view and edit");
    w->addTo(this);

    arrangeRow();

    w = new WButton(ID_LAYERADD, "Add...", Dialog::BUTTON_NOTHING, NEW_LAYER);
    w->setToolTip("Add a new layer");
    w->addTo(this);

    arrangeRow();

    runAsPanel();
}

// Sprite panel

FrameWindow* SceneEditSpriteList::createWindowed() { start_func
    // Prevent duplication
    if (myFrame) {
        return myFrame;
    }

    // We remember the frame pointer even though it'll delete itself
    myFrame = new FrameWindow("Objects", FrameWindow::RESIZING_SNAP, FrameWindow::FRAMETYPE_DIALOG, this, FrameWindow::TITLEBAR_TOOL, 0);
    return myFrame;
}

Window::WindowSort SceneEditSpriteList::windowSort() const { start_func
    return WINDOWSORT_ONTOP;
}

int SceneEditSpriteList::wantsToBeDeleted() const { start_func
    return 1;
}

SceneEditSpriteList::~SceneEditSpriteList() { start_func
    if (spriteList) {
        WidgetScroll* ws = dynamic_cast<WidgetScroll*>(spriteList->getParent());
        if (ws) ws->newClient();
        delete spriteList;
    }
}

SceneEditSpriteList::SceneEditSpriteList(SceneEditLayer* tPaintArea, WorldEdit* tWorld) : Dialog(blankString, 1, 0, 1) { start_func
    paintArea = tPaintArea;
    world = tWorld;
    buildingList = 0;

    myFrame = NULL;

    spriteList = new TreeView(blankString, NULL, 0, NULL, 1);
    buildSpriteList();
    // @TODO: add ::event and catch add/delete scripts
    buildingList = 1;
    spriteList->addTo(this, -1, 6);
    buildingList = 0;
    // @TODO: Tooltips

    arrangeRow();

    runAsPanel();
}

void SceneEditSpriteList::buildSpriteList() { start_func
    TreeView* scripts;
    TreeView* item;

    buildingList = 1;

    spriteList->insert(scripts = new TreeView("(custom object)", this, 1, treeViewWrapOther), 0);
    // @TODO: spriteList->insert(new TreeView("Libraries", this, 0, treeViewWrapOther), 0);
    // @TODO: spriteList->insert(new TreeView("Recent", this, 0, treeViewWrapOther), 0);
    spriteList->insert(scripts = new TreeView("Scripts", this, 0, treeViewWrapOther), 0);
    // @TODO: spriteList->insert(new TreeView("Used in Scene", this, 0, treeViewWrapOther), 0);
    
    World::ScriptIndex::const_iterator end = world->endScript();
    for (World::ScriptIndex::const_iterator pos = world->beginScript(); pos != end; ++pos) {
        ScriptEdit* script = dynamic_cast<ScriptEdit*>((*pos).second);
        if (script->getType() == Script::SCRIPT_CODE) {
            scripts->insert(item = new TreeView(script->getName(), this, script->getId(), treeViewWrapScript), 0);
            item->setIcon(script->getDefaultId() ? 13 : 12);
        }
    }
    
    buildingList = 0;
}

int SceneEditSpriteList::treeViewWrapScript(void* ptr, int code, int command, int check) { start_func
    return ((SceneEditSpriteList*)ptr)->treeViewScript(code, command, check);
}

int SceneEditSpriteList::treeViewScript(int code, int command, int check) { start_func
    if (check) {
        return Window::COMMAND_HIDE;
    }
    
    if (buildingList) return 0;

    if ((command == LV_MOVE) || (command == LV_LCLICK)) {
        ScriptEdit* script = dynamic_cast<ScriptEdit*>(world->findScript(code));
        if (script) {
            SpawnEdit* newSpawn = new SpawnEdit(world, NULL);
            newSpawn->markLock();
            newSpawn->setName(script->getName());
            newSpawn->setScript(script);
            newSpawn->setSprite(script->getDefaultAnimgroup(), script->getDefaultTileset(),
                                script->getDefaultId());

            paintArea->setSpawnPlace(newSpawn);
        }
        return 1;
    }

    return 0;
}

int SceneEditSpriteList::treeViewWrapOther(void* ptr, int code, int command, int check) { start_func
    return ((SceneEditSpriteList*)ptr)->treeViewOther(code, command, check);
}

int SceneEditSpriteList::treeViewOther(int code, int command, int check) { start_func
    if (check) {
        return Window::COMMAND_HIDE;
    }

    if (buildingList) return 0;

    if ((command == LV_MOVE) || (command == LV_LCLICK)) {
        if (code == 1) {
            // Blank starting point
            SpawnEdit* newSpawn = new SpawnEdit(world, NULL);
            newSpawn->markLock();
            paintArea->setSpawnPlace(newSpawn);
        }
        
        return 1;
    }

    return 0;
}

// Main edit window

SceneEditLayer::SceneEditLayer(SceneEdit* myScene) : selectedSpawns(), backupSelectedSpawns() throw_File { start_func
    assert(myScene);

    cursorX = cursorY = virtualCursorX = virtualCursorY = 0;
    cursorSpriteX = cursorSpriteY = 0;
    cursorW = cursorH = 1;
    haveFocus = partialFocus = 0;
    toolActive = startToolOnMove = 0;
    dirtyRange.w = 0;
    selectionRect.w = 0;
    selectionMode = SELECTION_OUTLINE;
    selectionXOffs = 0;
    selectionYOffs = 0;
    selectedSpawnRect.w = 0;
    backupSelectedSpawnRect.w = 0;

    scene = myScene;
    world = scene->getWorldEdit();
    scene->markLock(); // Exception point

    // Intentionally doesn't remember last tool selected
    // If we ever do, we must ensure both tools in this startup position
    // are sprite or non-sprite tools
    toolL = TOOLS_PEN;
    toolR = TOOLS_DROPPER;
    spriteMode = 0;
    spriteShown = 0;
    spriteAtCursor = 0;
    spawnPlace = new SpawnEdit(world, NULL);
    spawnPlace->markLock();
    spawnPropertiesDone = 0;
    contiguous = 1;
    // Recall last settings
    enableGrid = config->readNum(LAYEREDIT_GRID) ? 1 : 0;

    myFrame = NULL;
    tools = NULL;
    imagebar = NULL;
    colorbar = NULL;
    layerlist = NULL;
    spritelist = NULL;
    selected = NULL;
    backupSelected = NULL;
    for (int pos = 0; pos < MAX_LAYERS; ++pos) {
        layerEdit[pos][0] = NULL;
        layerEdit[pos][1] = NULL;
        layerEdit[pos][2] = NULL;
    }

    FrameWindow* toolsFrame = NULL;
    FrameWindow* imagebarFrame = NULL;
    FrameWindow* colorbarFrame = NULL;
    FrameWindow* layerlistFrame = NULL;
    FrameWindow* spritelistFrame = NULL;

    try {
        // Init frame up here so reloadLayerStats sets it's scroll size properly
        myFrame = new FrameWindow(blankString, FrameWindow::RESIZING_NORMAL, FrameWindow::FRAMETYPE_BEVEL_BK, this);
        myFrame->setAutoCenter(1);

        // First call to this determines our cursor layer
        initAvailableLayers();
        reloadLayerStats(1);
        refreshData(0);

        LayerEdit* layer = scene->getLayerEdit(cursorLayer);
        TileSetEdit* tileset = layer->getTileSetEdit();
        
        imagebar = new ImageSelect(tileset, &colors); // One exception point (others?)
        colorbar = new ColorSelect(&colors, 0, 0, Layer::LAYER_EXT_ALPHA_BITDEPTH, Layer::LAYER_TILE_COLOR_BITDEPTH);
        tools = new SceneEditLayerTools(this, &toolL, &toolR, &contiguous, &enableGrid);
        layerlist = new SceneEditLayerList(this);
        spritelist = new SceneEditSpriteList(this, world);
        setUsesExt(layerExt);
        
        imagebarFrame = imagebar->createWindowed();
        colorbarFrame = colorbar->createWindowed();
        toolsFrame = tools->createWindowed();
        layerlistFrame = layerlist->createWindowed();
        spritelistFrame = spritelist->createWindowed();
        
        // Second call to this fills our layer list
        initAvailableLayers();
        
        updateTitlebar();
        myFrame->addToolPanel(imagebarFrame, FrameWindow::CLIENT_BOTTOM);
        myFrame->addToolPanel(colorbarFrame, FrameWindow::CLIENT_RIGHT);
        myFrame->addToolPanel(toolsFrame, FrameWindow::CLIENT_RIGHT);
        myFrame->addToolPanel(layerlistFrame, FrameWindow::CLIENT_RIGHT);
        myFrame->addToolPanel(spritelistFrame, FrameWindow::CLIENT_RIGHT);
        myFrame->show(FrameWindow::SHOW_CASCADE, FrameWindow::SHOW_CASCADE, FrameWindow::SHOW_CURRENT, FrameWindow::SHOW_CURRENT);
        imagebar->colorRefresh();
    }
    catch (...) {
        if (myFrame) {
            myFrame->dropClients();
            delete myFrame;
            myFrame = NULL;
        }
        if (spritelistFrame) {
            delete spritelistFrame;
        }
        else {
            delete spritelist;
        }
        if (layerlistFrame) {
            delete layerlistFrame;
        }
        else {
            delete layerlist;
        }
        if (toolsFrame) {
            delete toolsFrame;
        }
        else {
            delete tools;
        }
        if (colorbarFrame) {
            delete colorbarFrame;
        }
        else {
            delete colorbar;
        }
        if (imagebarFrame) {
            delete imagebarFrame;
        }
        else {
            delete imagebar;
        }
        for (int pos = 0; pos < MAX_LAYERS; ++pos) {
            delete[] layerEdit[pos][0];
            delete[] layerEdit[pos][1];
            delete[] layerEdit[pos][2];
        }
        delete[] selected;
        delete[] backupSelected;
        scene->markUnlock();
        throw;
    }

    // Disabled/etc status on various tools
    updateCopyTool();
}

SceneEditLayer::~SceneEditLayer() { start_func
    for (int pos = 0; pos < MAX_LAYERS; ++pos) {
        delete[] layerEdit[pos][0];
        delete[] layerEdit[pos][1];
        delete[] layerEdit[pos][2];
    }
    delete[] selected;
    delete[] backupSelected;
    delete spawnPlace;
    if (scene) scene->markUnlock();
}

void SceneEditLayer::updateCopyTool() { start_func
    if ((selectionRect.w) || (selectedSpawnRect.w)) tools->findWidget(SceneEditLayerTools::ID_COPY)->enable();
    else tools->findWidget(SceneEditLayerTools::ID_COPY)->disable();
}

void SceneEditLayer::setSpawnPlace(class SpawnEdit* newSpawn) {
    delete spawnPlace;
    spawnPlace = newSpawn;
    spawnPropertiesDone = 0;
    
    if ((toolL != TOOLS_PLACEITEM) && (toolR != TOOLS_PLACEITEM) &&
        (toolL != TOOLS_PLACESERIES) && (toolR != TOOLS_PLACESERIES)) {
        desktop->broadcastEvent(SDL_COMMAND, TOOLS_PLACEITEM);
        desktop->broadcastEvent(SDL_COMMAND, CMD_RELEASE);
    }

    // @TODO: Dirty sprite area only
    setDirty(1);
}

void SceneEditLayer::setDirtyRect(const Rect& rect, int selectionFeather) { start_func
    // Clip
    Rect bound = { 0, 0, layerWidth, layerHeight };
    if (intersectRects(bound, rect)) {
        // Scale to tile size
        bound.x *= tileWidth;
        bound.y *= tileHeight;
        bound.w *= tileWidth;
        bound.h *= tileHeight;
        
        // Feather for selection modification?
        if (selectionFeather) {
            if (bound.x > 0) {
                --bound.x;
                ++bound.w;
            }
            if (bound.y > 0) {
                --bound.y;
                ++bound.h;
            }
            // We don't bother checking if our w/h goes over the limit-
            // this will get clipped; but negative x/y is too odd to allow, above
            ++bound.w;
            ++bound.h;
        }

        // Add rectangle into dirty range
        boundRects(dirtyRange, bound);
        setDirty();
    }
}

void SceneEditLayer::setDirtyBox(int x1, int y1, int x2, int y2, int selectionFeather) { start_func
    // Create a rectangle
    // Add rectangle into dirty range
    setDirtyRect(createRect(x1, y1, x2, y2), selectionFeather);
}

void SceneEditLayer::setDirtyPixelBox(int x1, int y1, int x2, int y2) { start_func
    boundRects(dirtyRange, createRect(x1, y1, x2, y2));
    setDirty();
}

void SceneEditLayer::setUsesExt(int usesExt) { start_func
    if (colorbar) colorbar->alphaDepth(usesExt ? Layer::LAYER_EXT_ALPHA_BITDEPTH : 0);
    if (imagebar) imagebar->allowAlpha(usesExt);
}

void SceneEditLayer::siblingModified(Window* modified) { start_func
    // Img selector updates with any color change
    if (modified == colorbar) {
        if (imagebar) imagebar->colorRefresh();
    }
}

// Note: any change to this function may need to also propogate to OBJMOD_TILES handler (+ OBJMOD_COLL?)
void SceneEditLayer::refreshData(int cursorLayerOnly, int x1, int y1, int x2, int y2) { start_func
    assert(scene);
    
    Rect affect = createRect(x1, y1, x2, y2);
    Rect bound = { 0, 0, layerWidth, layerHeight };
    if (!intersectRects(affect, bound)) return;
    
    Uint32 bit = 1;
    int pos = 0;
    int max = numLayers - 1;
    if (cursorLayerOnly) {
        pos = max = cursorLayer;
        if (pos) bit <<= pos;
    }
    for (; pos <= max; ++pos, bit <<= 1) {
        if (layersAffect & bit) {
            scene->getLayerEdit(pos)->loadLayer(layerEdit[pos][0] + affect.x + affect.y * layerWidth,
                                                layerEdit[pos][1] + affect.x + affect.y * layerWidth,
                                                layerEdit[pos][2] + affect.x + affect.y * layerWidth,
                                                layerWidth, affect);
        }
    }
}

void SceneEditLayer::applyData(int cursorLayerOnly, int x1, int y1, int x2, int y2) { start_func
    assert(scene);
    
    Rect affect = createRect(x1, y1, x2, y2);
    Rect bound = { 0, 0, layerWidth, layerHeight };
    if (!intersectRects(affect, bound)) return;
    
    Uint32 bit = 1;
    int pos = 0;
    int max = numLayers - 1;
    if (cursorLayerOnly) {
        pos = max = cursorLayer;
        if (pos) bit <<= pos;
    }
    for (; pos <= max; ++pos, bit <<= 1) {
        if (layersAffect & bit) {
            scene->getLayerEdit(pos)->saveLayer(layerEdit[pos][0] + affect.x + affect.y * layerWidth,
                                                layerEdit[pos][1] + affect.x + affect.y * layerWidth,
                                                layerEdit[pos][2] + affect.x + affect.y * layerWidth,
                                                layerWidth, affect, this);
        }
    }

    // @TODO: preview redraw
}

void SceneEditLayer::initAvailableLayers() { start_func
    // Fill layer variables etc.
    numLayers = scene->getLayerCount();
    // (must be at least one layer)
    assert(numLayers);
    
    // @TODO: Remember current layer(s) during a session? (reopening windows)
    
    if (layerlist) {
        if (layerlist->layerList) {
            layerlist->layerList->clear();
            layerlist->layerList->setScene(scene);
        }
    }
    
    Uint32 bit = 1;
    layersAffect = 0;
    layersDim = 0;
    layersAvailable = 0;
    layersView = 0;
    for (int pos = 0; pos < numLayers; ++pos, bit <<= 1) {
        layersView |= bit;
        Layer* layer = scene->getLayer(pos);
        if (layer->getType() == Layer::LAYER_TILE) {
            layersAvailable |= bit;
            if (!layersAffect) {
                layersAffect = bit;
                cursorLayer = pos;
            }
        }

        // All layers get added
        if (layerlist) {
            if (layerlist->layerList) {
                // (select item if cursor layer)
                layerlist->layerList->WListBox::addItem(ListEntry(layer->getName(), pos,
                                                        ((layersView & bit) ? WLayerListBox::FLAG_VISIBLE : 0) |
                                                        ((layersAffect & bit) ? WLayerListBox::FLAG_EDITABLE : 0),
                                                        layer->getType(),
                                                        0, !(layersAvailable & bit)),
                                                        cursorLayer == pos);
            }
        }
    }
    
    // (must be at least one tile layer)
    assert(layersAffect);
}

void SceneEditLayer::reloadLayerStats(int resizeView) { start_func
    assert(cursorLayer >= 0);
    assert(cursorLayer < numLayers);

    // Retain old selection?
    int retainSelData = 0;
    Uint8* oldSelected = NULL;
    int oldSelectedPitch;
    int oldSelectedHeight;
    if (selectionRect.w) {
        oldSelected = selected;
        oldSelectedPitch = selectedPitch;
        oldSelectedHeight = layerHeight;
        // Retain contents of a floating selection?
        if (selectionMode != SELECTION_OUTLINE) retainSelData = 1;
    }
    else {
        delete[] selected;
        selectionMode = SELECTION_OUTLINE;
    }

    delete[] backupSelected;
    selected = NULL;
    backupSelected = NULL;
    
    // Old sizes for retaining data
    int prevWidth = layerWidth;
    int prevHeight = layerHeight;

    // Get stats on current layer
    LayerEdit* layer = scene->getLayerEdit(cursorLayer);
    layerWidth = layer->getXSize();
    layerHeight = layer->getYSize();
    layerExt = layer->getUsesExt();
    setUsesExt(layerExt);

    TileSetEdit* tileset = layer->getTileSetEdit();
    numTiles = tileset->getCount();
    tileWidth = tileset->getWidth();
    tileHeight = tileset->getHeight();
    // Layer should have already locked tileset- exception should never happen
    try {
        if (imagebar) imagebar->changeSet(tileset);
    }
    catch (FileException& e) {
        fatalCrash(0, "Error reading tileset- %s", e.details);
    }
    
    // Clip cursor
    if (cursorX >= layerWidth) cursorX = layerWidth - 1;
    if (cursorY >= layerHeight) cursorY = layerHeight - 1;
    
    // Allocate selection storage
    selectedPitch = (layerWidth + 7) / 8;
    selected = new Uint8[selectedPitch * layerHeight];
    backupSelected = new Uint8[selectedPitch * layerHeight];
    memset(selected, 0, selectedPitch * layerHeight);
    memset(backupSelected, 0, selectedPitch * layerHeight);
    
    // Copy over saved selection
    if (oldSelected) {
        matrixCopy(oldSelected, selected, min(selectedPitch, oldSelectedPitch), min(oldSelectedHeight, layerHeight), oldSelectedPitch, selectedPitch);
        fixSelectionRect();
        delete[] oldSelected;
    }
    
    // Allocate layer storage
    Uint32 bit = 1;
    int affectedChange = 0;
    for (int pos = 0; pos < numLayers; ++pos, bit <<= 1) {
        if (layersAffect & bit) {
            // Ensure this layer has the same stats
            int same = 1;
            
            Layer* layer = scene->getLayer(pos);
            if ((layerWidth != layer->getXSize()) || (layerHeight != layer->getYSize())) same = 0;
        
            TileSet* tileset = layer->getTileSet();
            if ((tileWidth != tileset->getWidth()) || (tileHeight != tileset->getHeight())) same = 0;
            
            if (same) {
                int usesExt = layer->getUsesExt();
                int usesFx = layer->getUsesFx();
                int layerSize = layerWidth * layerHeight;
                
                // Retain a floating selection?
                if (retainSelData) {
                    Uint32* newEdit = NULL;
                    
                    newEdit = new Uint32[layerSize];
                    memSet32(newEdit, Layer::LAYER_TILE_DEFAULT, layerSize);
                    matrixCopy(layerEdit[pos][0], newEdit, min(prevWidth, layerWidth) * 4, min(prevHeight, layerHeight), prevWidth * 4, layerWidth * 4);
                    delete[] layerEdit[pos][0];
                    layerEdit[pos][0] = newEdit;
                    newEdit = NULL;

                    if (usesExt) {
                        newEdit = new Uint32[layerSize];
                        memSet32(newEdit, Layer::LAYER_EXT_DEFAULT, layerSize);
                        if (layerEdit[pos][1]) {
                            matrixCopy(layerEdit[pos][1], newEdit, min(prevWidth, layerWidth) * 4, min(prevHeight, layerHeight), prevWidth * 4, layerWidth * 4);
                            delete[] layerEdit[pos][1];
                        }
                        layerEdit[pos][1] = newEdit;
                        newEdit = NULL;
                    }
                    else {
                        delete[] layerEdit[pos][1];
                        layerEdit[pos][1] = NULL;
                    }

                    if (usesFx) {
                        newEdit = new Uint32[layerSize];
                        memSet32(newEdit, Layer::LAYER_FX_DEFAULT, layerSize);
                        if (layerEdit[pos][2]) {
                            matrixCopy(layerEdit[pos][2], newEdit, min(prevWidth, layerWidth) * 4, min(prevHeight, layerHeight), prevWidth * 4, layerWidth * 4);
                            delete[] layerEdit[pos][2];
                        }
                        layerEdit[pos][2] = newEdit;
                        newEdit = NULL;
                    }
                    else {
                        delete[] layerEdit[pos][2];
                        layerEdit[pos][2] = NULL;
                    }
                }
                else {
                    // Allocate storage (no need to fill with empty- layer will load over it)
                    delete[] layerEdit[pos][0];
                    layerEdit[pos][0] = NULL;
                    layerEdit[pos][0] = new Uint32[layerSize];
                    delete[] layerEdit[pos][1];
                    layerEdit[pos][1] = NULL;
                    delete[] layerEdit[pos][2];
                    layerEdit[pos][2] = NULL;

                    if (usesExt) layerEdit[pos][1] = new Uint32[layerSize];
                    if (usesFx) layerEdit[pos][2] = new Uint32[layerSize];
                }
            }
            else {
                // Layer is no longer "affected"
                layersAffect &= ~bit;
                affectedChange = 1;
            }
        }
        
        if (!(layersAffect & bit)) {
            // Free any previously allocated memory
            delete[] layerEdit[pos][0];
            delete[] layerEdit[pos][1];
            delete[] layerEdit[pos][2];
            layerEdit[pos][0] = NULL;
            layerEdit[pos][1] = NULL;
            layerEdit[pos][2] = NULL;
        }
    }
    
    // Update layers list if we modified layersAffect
    if ((affectedChange) && (layerlist)) {
        if (layerlist->layerList) {
            layerlist->layerList->setBitMask(layersAffect, layersView, layersDim);
        }
    }

    // Resize to match current layer
    setDirty(1);
    resize(tileWidth * layerWidth, tileHeight * layerHeight);
    if (myFrame) {
        myFrame->setScroll(tileWidth, tileHeight);
        if (resizeView) myFrame->requestViewSize(width, height);
    }
}

// @TODO: call when layer changes
void SceneEditLayer::updateTitlebar() { start_func
    if ((myFrame) && (scene)) {
        myFrame->setTitle(formatString("%s : %s : Layer %s", world->getTitle().c_str(), scene->getName().c_str(), scene->getLayer(cursorLayer)->getName().c_str()));
    }
}

int SceneEditLayer::event(int hasFocus, const SDL_Event* event) { start_func
    int changed = 0;
    int clickX, clickY;
    ObjChange* obj;

    switch (event->type) {
        case SDL_CLOSE:
            // Merge any existing selection
            try {
                mergeSelection();
            }
            catch (UndoException& e) {
            }
            return 1;
    
        case SDL_SPECIAL:
            // Refresh selection rectangles?
            if ((event->user.code == SDL_IDLEPHASE) && (partialFocus)) {
                Rect rect = { selectionRect.x + selectionXOffs, selectionRect.y + selectionYOffs, selectionRect.w, selectionRect.h };
                setDirtyRect(rect);
                setDirtySelectedSpawns();
                if (toolActive == TOOLS_SELECTITEM)
                    setDirtyPixelBox(toolStartX, toolStartY, toolLastX, toolLastY);
            }
            // Refresh cursor?
            if (((event->user.code == SDL_IDLECURSOR) || (event->user.code == SDL_IDLEPHASE)) && (haveFocus)) {
                Rect rect = { cursorX, cursorY, cursorW, cursorH };
                setDirtyRect(rect);
            }
            return 1;

        case SDL_COMMAND:
            switch (event->user.code) {
                case EDIT_UNDO:
                    // finish instead of cancel- this causes undo to undo current
                    // tool if one is being used
                    finishTool();
                    // (let world handle from here)
                    break;
                    
                case EDIT_REDO:
                    cancelTool();
                    // (let world handle from here)
                    break;

                case EDIT_COPY:
                    if (useSpawnSelection()) copySpawnSelection();
                    else copySelection();
                    return 1;
                    
                case EDIT_CUT:
                    try {
                        world->undo.preUndoBlock();
                        if (useSpawnSelection()) {
                            copySpawnSelection();
                            deleteSpawnSelection();
                            clearSpawnSelection();
                        }
                        else {
                            copySelection();
                            deleteSelection();
                            clearSelection();
                        }
                        world->undo.postUndoBlock();
                    }
                    catch (UndoException& e) {
                    }
                    return 1;
                    
                case EDIT_DELETE:
                    try {
                        world->undo.preUndoBlock();
                        deleteSpawnSelection();
                        deleteSelection();
                        world->undo.postUndoBlock();
                    }
                    catch (UndoException& e) {
                    }
                    return 1;
                    
                case EDIT_SELECTALL:
                    try {
                        if (spriteMode) {
                            undoStoreSpawnSelect();

                            selectSpawnRect(0, 0, 0, 0, 1, 1);
                            recheckSpawnSelection();
                            setDirtySelectedSpawns();
                            
                            // Ensure selection tool active
                            if ((!toolActive) && (toolL != TOOLS_SELECTITEM)) {
                                desktop->broadcastEvent(SDL_COMMAND, TOOLS_SELECTITEM);
                                desktop->broadcastEvent(SDL_COMMAND, CMD_RELEASE);
                            }
                        }
                        else {
                            world->undo.preUndoBlock();
                            // Merge any floating
                            mergeSelection();
                            // Store undo for entire selection
                            undoStoreSelect(0, 0, layerWidth, layerHeight);
                            world->undo.postUndoBlock();
    
                            // Select all
                            memset(selected, 255, selectedPitch * layerHeight);
                            selectionRect.x = 0;
                            selectionRect.y = 0;
                            selectionRect.w = layerWidth;
                            selectionRect.h = layerHeight;
                            setDirty(1);
                        }
                        updateCopyTool();
                    }
                    catch (UndoException& e) {
                    }

                    return 1;
                    
                case EDIT_DESELECTALL:
                    cancelTool();
                    try {
                        doneSelection();
                        clearSpawnSelection();
                    }
                    catch (UndoException& e) {
                    }
                    return 1;
                    
                case EDIT_PASTE:
                    try {
                        changed = pasteSelection();
                    }
                    catch (UndoException& e) {
                    }
                    
                    // Make sure a selection tool is current
                    if (changed == TOOLS_SELECT) {
                        if ((!toolActive) && (toolL != TOOLS_SELECT) && (toolL != TOOLS_SELECTELLIPSE) && (toolL != TOOLS_WAND)) {
                            desktop->broadcastEvent(SDL_COMMAND, TOOLS_SELECT);
                            desktop->broadcastEvent(SDL_COMMAND, CMD_RELEASE);
                        }
                    }
                    else if (changed == TOOLS_SELECTITEM) {
                        if ((!toolActive) && (toolL != TOOLS_SELECTITEM)) {
                            desktop->broadcastEvent(SDL_COMMAND, TOOLS_SELECTITEM);
                            desktop->broadcastEvent(SDL_COMMAND, CMD_RELEASE);
                        }
                    }
                    
                    return 1;
                    
                case CMD_RELEASE:
                    finishTool(); // Clears startToolOnMove too; safe even if no tool
                    moveCursor(cursorSpriteX, cursorSpriteY);
                    return 1;
                    
                case NEW_LAYER:
                    try {
                        scene->newLayer(myFrame);
                    }
                    catch (FileException& e) {
                        guiErrorBox(string(e.details), errorTitleFile);
                    }
                    return 1;
                    
                case TOOLS_NEXTIMAGE:
                    imagebar->nextImage();
                    return 1;
                    
                case TOOLS_PREVIMAGE:
                    imagebar->prevImage();
                    return 1;

                case TOOLS_NEXTCOLOR:
                    colorbar->colorSelection(ColorSelect::SELECTED_FG,
                                             colorbar->colorSelection(ColorSelect::SELECTED_FG) + 1);
                    return 1;

                case TOOLS_PREVCOLOR:
                    colorbar->colorSelection(ColorSelect::SELECTED_FG,
                                             colorbar->colorSelection(ColorSelect::SELECTED_FG) - 1);
                    return 1;

                case TOOLS_EDITCOLOR:
                    colorbar->editColor(colorbar->colorSelection(ColorSelect::SELECTED_FG));
                    return 1;
            }
            
            if (world->commandEvent(event->user.code)) return 1;
            break;

        case SDL_OBJECTCHANGE:
            if (!scene) return 0;
            obj = (ObjChange*)event->user.data1;
            if (event->user.code & OBJ_SPAWN) {
                // One of our layers?
                SpawnEdit* spawn = (SpawnEdit*)obj->obj;
                Layer* spawnLayer = spawn->getLayer();
                if (!spawnLayer) break;
                
                // Find out if it matches one of our layers (and if we're editing that layer)
                Uint32 bit = 1;
                int layerNum = -1;
                int affected = 0;
                for (int pos = 0; pos < numLayers; ++pos, bit <<= 1) {
                    Layer* layer = scene->getLayer(pos);
                    if (spawnLayer == layer) {
                        layerNum = pos;
                        affected = layersAffect & bit;
                        break;
                    }
                }
                
                // Not one of our layers
                if (layerNum < 0) break;

                setDirty(1);

                // Any change could affect our selection boundaries
                if ((affected) && (selectedSpawns.count(spawn->getId()))) {
                    if (event->user.code & OBJMOD_DELETE)
                        selectedSpawns.erase(spawn->getId());
                    recheckSpawnSelection();
                }
            }
            if (event->user.code & OBJ_LAYER) {
                // Find out if it matches one of our layers (and if we're editing that layer)
                Uint32 bit = 1;
                int layerNum = -1;
                int affected = 0;
                for (int pos = 0; pos < numLayers; ++pos, bit <<= 1) {
                    Layer* layer = scene->getLayer(pos);
                    if (obj->obj == layer) {
                        layerNum = pos;
                        affected = layersAffect & bit;
                        break;
                    }
                }
                
                // Not one of our layers
                if (layerNum < 0) break;
        
                if (event->user.code & OBJMOD_TILES) {
                    if (affected) {
                        if (selectionMode == SELECTION_OUTLINE) {
                            Rect affect = { 0, 0, layerWidth, layerHeight };
                            scene->getLayerEdit(layerNum)->loadLayer(layerEdit[layerNum][0], layerEdit[layerNum][1], layerEdit[layerNum][2], layerWidth, affect);
                        }
                    }
                    setDirty(1);
                }
                if (event->user.code & OBJMOD_NAME) {
                    layerlist->layerList->modifyItem(layerNum, &((Layer*)(obj->obj))->getName());
                }
                if ((event->user.code & OBJMOD_WIDTH) || (event->user.code & OBJMOD_HEIGHT) || (event->user.code & OBJMOD_USESFX) || (event->user.code & OBJMOD_USESEXT) || (event->user.code & OBJMOD_TILESET)) {
                    if (affected) {
                        // @TODO: undo won't properly undo the loss of size on the selection
                        //        we need to have a layer that resizes scan all windows and
                        //        resize all appropriate selections as part of it's undo block
                        reloadLayerStats();
                        if (selectionMode == SELECTION_OUTLINE) refreshData(0);
                    }
                    else {
                        setDirty(1);
                    }
                }
                if (event->user.code & OBJMOD_TYPE) {
                    // Layer becomes available if not already; nothing else affected except type icon
                    int type = ((Layer*)(obj->obj))->getType();
                    Uint32 newLayersAvailable = layersAvailable;
                    Uint32 newLayersAffect = layersAffect;
                    if (type == Layer::LAYER_TILE) newLayersAvailable |= bit;
                    else {
                        newLayersAvailable &= ~bit;
                        newLayersAffect &= ~bit;
                    }
                    // No layers to affect anymore? Close
                    if (!newLayersAvailable) {
                        scene->markUnlock();
                        scene = NULL;
                        closeWindow();
                        return 1;
                    }
                    // No affected layers? pick first one
                    if (!newLayersAffect) {
                        newLayersAffect = 1;
                        while (!(newLayersAvailable & newLayersAffect)) {
                            newLayersAffect <<= 1;
                        }
                    }
                    // Update layers list
                    int disable = !(newLayersAvailable & bit);
                    layerlist->layerList->modifyItem(layerNum, NULL, &disable, NULL, &type);
                    // (this fixes cursor layer if it needs to be)
                    layersAvailable = newLayersAvailable;
                    refreshLayers(newLayersAffect, layersView, layersDim);
                }
            }
            if (event->user.code & OBJ_TILESET) {
                LayerEdit* layer = scene->getLayerEdit(cursorLayer);
                TileSetEdit* tileset = layer->getTileSetEdit();
                if (obj->obj == (TileSet*)tileset) {
                    // @TODO: OBJMOD_COUNTCOLL- 
                    // stuff will need reloading once we can edit collisions
                    if ((event->user.code & OBJMOD_WIDTH) || (event->user.code & OBJMOD_HEIGHT) || (event->user.code & OBJMOD_COUNT) || (event->user.code & OBJMOD_TILE)) {
                        // Width/height require actual layer stat change
                        if ((event->user.code & OBJMOD_WIDTH) || (event->user.code & OBJMOD_HEIGHT)) {
                            reloadLayerStats();
                            if (selectionMode == SELECTION_OUTLINE) refreshData(0);
                        }
                        
                        // All require visual refresh and imagebar adjustment
                        setDirty(1);
                        // Layer should have already locked tileset- exception should never happen
                        try {
                            if (imagebar) imagebar->changeSet(tileset);
                        }
                        catch (FileException& e) {
                            fatalCrash(0, "Error reading tileset- %s", e.details);
                        }
                    }
                }
            }
            if ((event->user.code & OBJ_SCENE) && (obj->obj == scene)) {
                if (event->user.code & OBJMOD_LAYERDEL) {
                    // Update our data
                    --numLayers;
                    
                    // Determine which bits "shift down" to fill hole
                    Uint32 bitsToShift = 0;
                    for (Uint32 bit = 2 << obj->info1; bit; bit <<= 1) {
                        bitsToShift |= bit;
                    }
                    Uint32 bitsToLeave = (bitsToShift ^ 0xFFFFFFFF) ^ (1 << obj->info1);
                    
                    layersView = (layersView & bitsToLeave) | ((layersView & bitsToShift) >> 1);
                    layersDim = (layersDim & bitsToLeave) | ((layersDim & bitsToShift) >> 1);
                    layersAffect = (layersAffect & bitsToLeave) | ((layersAffect & bitsToShift) >> 1);
                    layersAvailable = (layersAvailable & bitsToLeave) | ((layersAvailable & bitsToShift) >> 1);
                    
                    // Shift data down
                    delete[] layerEdit[obj->info1][0];
                    delete[] layerEdit[obj->info1][1];
                    delete[] layerEdit[obj->info1][2];
                    for (int pos = obj->info1 + 1; pos < MAX_LAYERS; ++pos) {
                        layerEdit[pos - 1][0] = layerEdit[pos][0];
                        layerEdit[pos - 1][1] = layerEdit[pos][1];
                        layerEdit[pos - 1][2] = layerEdit[pos][2];
                    }
                    layerEdit[MAX_LAYERS - 1][0] = NULL;
                    layerEdit[MAX_LAYERS - 1][1] = NULL;
                    layerEdit[MAX_LAYERS - 1][2] = NULL;
                    
                    // Cursor layer- shift down? if cursorlayer was deleted, "no" cursor layer
                    if (cursorLayer == obj->info1) cursorLayer = -1;
                    else if (cursorLayer > obj->info1) --cursorLayer;

                    // No layers to affect anymore? Close
                    if (!layersAvailable) {
                        scene->markUnlock();
                        scene = NULL;
                        closeWindow();
                        return 1;
                    }
    
                    // Remove from layer list (this might call widget/childmodified
                    // leading to refreshlayers)
                    layerlist->layerList->removeItem(obj->info1);

                    // Determine new stats (this may attempt a merge selection,
                    // which should be safe even if no cursorlayer/affectedlayer)
                    refreshLayers();
                    
                    // Update screen
                    setDirty(1);
                }
                if (event->user.code & OBJMOD_LAYERADD) {
                    // We need to double check that this layer isn't already added
                    // (can happen when first creating a scene)
                    if (scene->getLayerCount() > numLayers) {
                        // Determine which bits "shift up" to open hole
                        Uint32 bitsToShift = 0;
                        for (Uint32 bit = 1 << obj->info1; bit; bit <<= 1) {
                            bitsToShift |= bit;
                        }
                        Uint32 bitsToLeave = bitsToShift ^ 0xFFFFFFFF;
                        
                        layersView = (layersView & bitsToLeave) | ((layersView & bitsToShift) << 1);
                        layersDim = (layersDim & bitsToLeave) | ((layersDim & bitsToShift) << 1);
                        layersAffect = (layersAffect & bitsToLeave) | ((layersAffect & bitsToShift) << 1);
                        layersAvailable = (layersAvailable & bitsToLeave) | ((layersAvailable & bitsToShift) << 1);
                    
                        // Shift data up
                        for (int pos = MAX_LAYERS - 1; pos > obj->info1; --pos) {
                            layerEdit[pos][0] = layerEdit[pos - 1][0];
                            layerEdit[pos][1] = layerEdit[pos - 1][1];
                            layerEdit[pos][2] = layerEdit[pos - 1][2];
                        }
                        layerEdit[obj->info1][0] = NULL;
                        layerEdit[obj->info1][1] = NULL;
                        layerEdit[obj->info1][2] = NULL;
                    
                        // Cursor layer- shift up?
                        if (cursorLayer >= obj->info1) ++cursorLayer;

                        // Add layer, but not as editable until user selects
                        Uint32 bit = 1 << obj->info1;
                        layersView |= bit;
                        Layer* layer = scene->getLayer(obj->info1);
                        if (layer->getType() == Layer::LAYER_TILE) layersAvailable |= bit;
                    
                        layerlist->layerList->addItem(obj->info1,
                                                      ListEntry(layer->getName(), obj->info1,
                                                                (layersView & bit) ? WLayerListBox::FLAG_VISIBLE : 0,
                                                                layer->getType(), 0,
                                                                !(layersAvailable & bit)));
                        ++numLayers;
                        setDirty(1);
                    }
                }
                if (event->user.code & OBJMOD_LAYERMOVE) {
                    // If we swap all data to match, a move ultimately only affects
                    // the visual appearance (layers above/below each other)
                    Uint32 bit1 = 1 << obj->info1;
                    Uint32 bit2 = 1 << obj->info2;
                    Uint32 bits = bit1 | bit2;
                    // Swap appropriate bits, if only one of the two is set
                    if (((layersView & bits) == bit1) || ((layersView & bits) == bit2)) layersView ^= bits;
                    if (((layersDim & bits) == bit1) || ((layersDim & bits) == bit2)) layersDim ^= bits;
                    if (((layersAffect & bits) == bit1) || ((layersAffect & bits) == bit2)) layersAffect ^= bits;
                    if (((layersAvailable & bits) == bit1) || ((layersAvailable & bits) == bit2)) layersAvailable ^= bits;
                    // Swap cursor
                    if (cursorLayer == obj->info1) cursorLayer = obj->info2;
                    else if (cursorLayer == obj->info2) cursorLayer = obj->info1;
                    // Swap layer data
                    swap(layerEdit[obj->info1][0], layerEdit[obj->info2][0]);
                    swap(layerEdit[obj->info1][1], layerEdit[obj->info2][1]);
                    swap(layerEdit[obj->info1][2], layerEdit[obj->info2][2]);
                    // Swap in layer list
                    layerlist->layerList->swapItems(obj->info1, obj->info2);
                    // Update screen
                    setDirty(1);
                }
            }
            if ((event->user.code & OBJ_WORLD) && (obj->obj == world)) {
                if (event->user.code & OBJMOD_DELETE) {
                    imagebar->changeSet(NULL, 1);
                    scene = NULL;
                    closeWindow();
                }
                if (event->user.code & OBJMOD_NAME) {
                    updateTitlebar();
                }
            }
            return 1;
    
        case SDL_MOUSEBUTTONDOWN:
        case SDL_MOUSEBUTTONDBL:
            if ((event->button.button == SDL_BUTTON_LEFT) || (event->button.button == SDL_BUTTON_RIGHT)) {
                spriteAtCursor = 0;
                // Signed as it could go off the edge
                clickX = (Sint16)event->button.x;
                clickY = (Sint16)event->button.y;
                // If unmodified selection-tool click and within selection, this is a selection drag
                if ((!toolActive) &&
                    !(SDL_GetModState() & (KMOD_CTRL | KMOD_ALT)) && (
                    (((toolL == TOOLS_SELECT) || (toolL == TOOLS_SELECTELLIPSE) || (toolL == TOOLS_WAND)) &&
                     (event->button.button == SDL_BUTTON_LEFT)) ||
                    (((toolR == TOOLS_SELECT) || (toolR == TOOLS_SELECTELLIPSE) || (toolR == TOOLS_WAND)) &&
                     (event->button.button == SDL_BUTTON_RIGHT))
                    ) && (isInSelection(clickX / tileWidth, clickY / tileHeight))) {
                    startToolOnMove = 0;
                    moveCursor(clickX, clickY);
                    startToolSelectionDrag();
                }
                else if ((!toolActive) &&
                    !(SDL_GetModState() & (KMOD_CTRL | KMOD_ALT)) && (
                    ((toolL == TOOLS_SELECTITEM) &&
                     (event->button.button == SDL_BUTTON_LEFT)) ||
                    ((toolR == TOOLS_SELECTITEM) &&
                     (event->button.button == SDL_BUTTON_RIGHT))
                    ) && (isInSpawnSelection(clickX, clickY))) {
                    startToolOnMove = 0;
                    moveCursor(clickX, clickY);
                    startToolSpawnSelectionDrag();
                }
                else {
                    moveCursor(clickX, clickY);
                    startTool(event->button.button);
                }
                updateSpriteShown();
                return 1;
            }
            break;
    
        case SDL_MOUSEBUTTONUP:
            finishTool();
            
            // Force cursor back into area, no more virtual
            moveCursor(cursorSpriteX, cursorSpriteY);
            return 1;
    
        case SDL_MOUSEMOTION:
            spriteAtCursor = 0;
            pixelMouseX = (Sint16)event->motion.x;
            pixelMouseY = (Sint16)event->motion.y;
            if ((event->motion.state & SDL_BUTTON_LMASK) || (event->motion.state & SDL_BUTTON_RMASK)) {
                // Signed as it could go off the edge
                moveCursor((Sint16)event->motion.x, (Sint16)event->motion.y);
            }
            else {
                // Update mouse pointer
                mousePointer((Sint16)event->motion.x, (Sint16)event->motion.y);
            }
            updateSpriteShown();
            return 1;

        case SDL_MOUSEFOCUS:
            if (event->user.code & 1) {
                hover = 1;
                mousePointer();
            }
            else {
                hover = 0;
                selectMouse(MOUSE_NORMAL);
            }
            updateSpriteShown();
            return 1;
        
        case SDL_INPUTFOCUS:
            if (event->user.code & 1) {
                if (!haveFocus) {
                    haveFocus = partialFocus = 1;
                    changed = 1;
                }
            }
            else if (event->user.code & 2) {
                if (!partialFocus) {
                    partialFocus = 1;
                    changed = 1;
                }
            }
            else {
                if (partialFocus) {
                    partialFocus = 0;
                    changed = 1;
                }
            }
            if (!(event->user.code & 1)) {
                if (haveFocus) {
                    cancelTool();
                    haveFocus = 0;
                    changed = 1;
                }
            }
            
            if (changed) {
                // Refresh selection rectangle and cursor
                Rect rect = { selectionRect.x + selectionXOffs, selectionRect.y + selectionYOffs, selectionRect.w, selectionRect.h };
                Rect rect2 = { cursorX, cursorY, cursorW, cursorH };
                setDirtyRect(rect);
                setDirtyRect(rect2);
                setDirtySelectedSpawns();
                updateSpriteShown();
            }
            return 1;

        case SDL_KEYUP:
            switch (event->key.keysym.sym) {
                case SDLK_LSHIFT:
                case SDLK_RSHIFT:
                case SDLK_SPACE:
                    finishTool();

                    // Force cursor back into area, no more virtual
                    moveCursor(cursorSpriteX, cursorSpriteY);
                    return 1;
                
                case SDLK_LALT:
                case SDLK_RALT:
                case SDLK_LCTRL:
                case SDLK_RCTRL:
                    modifyTool();
                    return 1;
                
                default:
                    break;
            }
            break;

        case SDL_KEYDOWN:
            // We can't stick modifiers in due to the numerous combinations that
            // could occur of ctrl/shift/alt in use of various tools
            switch (event->key.keysym.sym) {
                case SDLK_KP_ENTER:
                case SDLK_RETURN:
                    // Merge any existing selection
                    try {
                        mergeSelection();
                    }
                    catch (UndoException& e) {
                    }
                    return 1;

                case SDLK_LALT:
                case SDLK_RALT:
                case SDLK_LCTRL:
                case SDLK_RCTRL:
                    modifyTool();
                    return 1;

                case SDLK_SPACE:
                    // (ensure on a tile boundary)
                    moveCursor(virtualCursorX * tileWidth, virtualCursorY * tileHeight);
                    startTool(SDL_BUTTON_LEFT);
                    changed = 1;
                    break;

                case SDLK_LSHIFT:
                    // (ensure on a tile boundary)
                    moveCursor(virtualCursorX * tileWidth, virtualCursorY * tileHeight);
                    startToolOnMove = 1;
                    return 1;
                        
                // @TODO: This should probably end up as a configurable shortcut somehow; should SHIFTs/SPACE too?
                case SDLK_RSHIFT:
                    // (ensure on a tile boundary)
                    moveCursor(virtualCursorX * tileWidth, virtualCursorY * tileHeight);
                    startTool(SDL_BUTTON_RIGHT);
                    changed = 1;
                    break;
                        
                case SDLK_RIGHT:
                    // @TODO: cursor drag of sprite (or regular?) selection
                    if (selectionMode != SELECTION_OUTLINE) {
                        if (event->key.keysym.mod & KMOD_CTRL) moveSelection(layerWidth - selectionRect.w - selectionRect.x, selectionYOffs);
                        else moveSelection(selectionXOffs + 1, selectionYOffs);
                    }
                    else {
                        if (event->key.keysym.mod & KMOD_CTRL) scanCursor(1, 0);
                        else moveCursor(virtualCursorX * tileWidth + tileWidth, virtualCursorY * tileHeight);
                    }
                    changed = 1;
                    break;
                        
                case SDLK_END:
                    if (selectionMode != SELECTION_OUTLINE) {
                        if (event->key.keysym.mod & KMOD_CTRL) moveSelection(layerWidth - selectionRect.w - selectionRect.x, layerHeight - selectionRect.h - selectionRect.y);
                        else moveSelection(layerWidth - selectionRect.w - selectionRect.x, selectionYOffs);
                    }
                    else {
                        if (event->key.keysym.mod & KMOD_CTRL) moveCursor((layerWidth - cursorW) * tileWidth, (layerHeight - cursorH) * tileHeight);
                        else moveCursor((layerWidth - cursorW) * tileWidth, virtualCursorY * tileHeight);
                    }
                    changed = 1;
                    break;
                        
                case SDLK_LEFT:
                    if (selectionMode != SELECTION_OUTLINE) {
                        if (event->key.keysym.mod & KMOD_CTRL) moveSelection(-selectionRect.x, selectionYOffs);
                        else moveSelection(selectionXOffs - 1, selectionYOffs);
                    }
                    else {
                        if (event->key.keysym.mod & KMOD_CTRL) scanCursor(-1, 0);
                        else moveCursor(virtualCursorX * tileWidth - tileWidth, virtualCursorY * tileHeight);
                    }
                    changed = 1;
                    break;

                case SDLK_HOME:
                    if (selectionMode != SELECTION_OUTLINE) {
                        if (event->key.keysym.mod & KMOD_CTRL) moveSelection(-selectionRect.x, -selectionRect.y);
                        else moveSelection(-selectionRect.x, selectionYOffs);
                    }
                    else {
                        if (event->key.keysym.mod & KMOD_CTRL) moveCursor(0, 0);
                        else moveCursor(0, virtualCursorY * tileHeight);
                    }
                    changed = 1;
                    break;
                        
                case SDLK_DOWN:
                    if (selectionMode != SELECTION_OUTLINE) {
                        if (event->key.keysym.mod & KMOD_CTRL) moveSelection(selectionXOffs, layerHeight - selectionRect.h - selectionRect.y);
                        else moveSelection(selectionXOffs, selectionYOffs + 1);
                    }
                    else {
                        if (event->key.keysym.mod & KMOD_CTRL) scanCursor(0, 1);
                        else moveCursor(virtualCursorX * tileWidth, virtualCursorY * tileHeight + tileHeight);
                    }
                    changed = 1;
                    break;

                case SDLK_UP:
                    if (selectionMode != SELECTION_OUTLINE) {
                        if (event->key.keysym.mod & KMOD_CTRL) moveSelection(selectionXOffs, -selectionRect.y);
                        else moveSelection(selectionXOffs, selectionYOffs - 1);
                    }
                    else {
                        if (event->key.keysym.mod & KMOD_CTRL) scanCursor(0, -1);
                        else moveCursor(virtualCursorX * tileWidth, virtualCursorY * tileHeight - tileHeight);
                    }
                    changed = 1;
                    break;

                case SDLK_PAGEDOWN:
                    if (selectionMode != SELECTION_OUTLINE) {
                        moveSelection(selectionXOffs, selectionYOffs + (viewHeight / tileHeight - 1));
                    }
                    else {
                        moveCursor(virtualCursorX * tileWidth, (virtualCursorY + (viewHeight / tileHeight - 1)) * tileHeight);
                    }
                    changed = 1;
                    break;

                case SDLK_PAGEUP:
                    if (selectionMode != SELECTION_OUTLINE) {
                        moveSelection(selectionXOffs, selectionYOffs - (viewHeight / tileHeight - 1));
                    }
                    else {
                        moveCursor(virtualCursorX * tileWidth, (virtualCursorY - (viewHeight / tileHeight - 1)) * tileHeight);
                    }
                    changed = 1;
                    break;
                
                default:
                    break;
            }
            if (changed) {
                spriteAtCursor = 1;
                updateSpriteShown();
                return 1;
            }
            break;
    }

    return 0;
}

void SceneEditLayer::scanCursor(int xDir, int yDir) { start_func
    assert(xDir || yDir);
    assert((xDir == -1) || (xDir == 0) || (xDir == 1));
    assert((yDir == -1) || (yDir == 0) || (yDir == 1));
    
    int x = virtualCursorX;
    int y = virtualCursorY;
    if (x < 0) x = 0;
    if (y < 0) y = 0;
    if (x >= layerWidth) x = layerWidth - 1;
    if (y >= layerHeight) y = layerHeight - 1;
    int offset = x + y * layerWidth;
    int type;
    int first = 1;
    do {
        x += xDir;
        y += yDir;
        if (x < 0) break;
        if (x >= layerWidth) break;
        if (y < 0) break;
        if (y >= layerHeight) break;
        offset += xDir + yDir * layerWidth;
        if (first) {
            first = 0;
            type = layerEdit[cursorLayer][0][offset] & Layer::LAYER_TILE_INDEX;
        }
        else if (layerEdit[cursorLayer][0][offset] & Layer::LAYER_TILE_INDEX) {
            if (!type) break;
        }
        else {
            if (type) {
                x -= xDir;
                y -= yDir;
                break;
            }
        }
    } while (1);
    
    moveCursor(x * tileWidth, y * tileHeight);
}

int SceneEditLayer::useSpawnSelection() const { start_func
    if (selectedSpawns.empty()) return 0;
    if (selectionRect.w) return spriteMode;
    return 1;
}

void SceneEditLayer::moveSelection(int newX, int newY) { start_func
    // No dragging of outlines allowed
    if (selectionMode == SELECTION_OUTLINE) return;
    
    if ((newX != selectionXOffs) || (newY != selectionYOffs)) {
        // Dirty old
        Rect rect = { selectionRect.x + selectionXOffs, selectionRect.y + selectionYOffs, selectionRect.w, selectionRect.h };
        setDirtyRect(rect, 1);
        
        // Move (no limits to where it can move to)
        selectionXOffs = newX;
        selectionYOffs = newY;
        
        // Dirty new
        rect.x = selectionRect.x + newX;
        rect.y = selectionRect.y + newY;
        setDirtyRect(rect, 1);
    }
}
    
void SceneEditLayer::moveCursor(int newX, int newY) { start_func
    int doTool = 0;
    int newTileX = newX / tileWidth;
    int newTileY = newY / tileHeight;
    
    if (startToolOnMove) startTool(SDL_BUTTON_LEFT);

    // This alone doesn't make anything dirty; normally, this isn't used;
    // anything that does use it, dirties; we don't track virtual cursor
    // if no active tool
    if (toolActive) {
        if ((newTileX != virtualCursorX) || (virtualCursorY != newTileY)) {
            virtualCursorX = newTileX;
            virtualCursorY = newTileY;
            
            // Tool
            if (!spriteMode) doTool = 1;
        }
    }

    if (newTileX + cursorW < 1) newTileX = 1 - cursorW;
    if (newTileY + cursorH < 1) newTileY = 1 - cursorH;
    if (newTileX >= layerWidth) newTileX = layerWidth - 1;
    if (newTileY >= layerHeight) newTileY = layerHeight - 1;
    
    if ((newTileX != cursorX) || (newTileY != cursorY)) {
        Rect rect = { cursorX, cursorY, cursorW, cursorH };
        setDirtyRect(rect);

        cursorX = newTileX;
        cursorY = newTileY;

        // Scroll?
        if (!spriteMode)
            if (myFrame) myFrame->scrollToView(cursorX * tileWidth, cursorY * tileHeight,
                                               cursorW * tileWidth, cursorH * tileHeight);

        // Dirty
        Rect rect2 = { cursorX, cursorY, cursorW, cursorH };
        setDirtyRect(rect2);
    }

    if ((newX != cursorSpriteX) || (newY != cursorSpriteY)) {
        cursorSpriteX = newX;
        cursorSpriteY = newY;

        // Scroll?
        // @TODO: account for sprite size
        if ((spriteMode) && (myFrame)) myFrame->scrollToView(cursorSpriteX, cursorSpriteY, 1, 1);

        if ((spriteMode) && (toolActive)) doTool = 1;
    }

    if (doTool) dragTool();
    // No active tool, we track virtual cursor, but to CLIPPED coordinates
    else if (!toolActive) {
        virtualCursorX = newTileX;
        virtualCursorY = newTileY;
    }
}

void SceneEditLayer::startToolSelectionDrag() { start_func
    startToolOnMove = 0;
    if (toolActive) return;

    toolActive = TOOLS_SELECTDRAG;
    toolStartX = virtualCursorX;
    toolStartY = virtualCursorY;
    toolCtrl = SDL_GetModState() & KMOD_CTRL;
    toolAlt = SDL_GetModState() & KMOD_ALT;
    mousePointer();
    dragTool(1);
}

void SceneEditLayer::startTool(int button) { start_func
    startToolOnMove = 0;
    if (toolActive) return;

    // Merge any existing selection
    try {
        mergeSelection();
    }
    catch (UndoException& e) {
        return;
    }

    toolMinX = virtualCursorX;
    toolMinY = virtualCursorY;
    toolMaxX = virtualCursorX;
    toolMaxY = virtualCursorY;
    
    toolActive = button == SDL_BUTTON_LEFT ? toolL : toolR;
    if (spriteMode) {
        toolStartX = cursorSpriteX;
        toolStartY = cursorSpriteY;
    }
    else {
        toolStartX = virtualCursorX;
        toolStartY = virtualCursorY;
    }
    toolCtrl = SDL_GetModState() & KMOD_CTRL;
    toolAlt = SDL_GetModState() & KMOD_ALT;
    toolContiguous = contiguous;
    
    imagebar->getDataExt(toolData, toolExt, toolFx);

    // @TODO: not determined: ambient, tweak, collision data, animation
    // @TODO: advanced mode where user determines what to place
    toolDataMask = Layer::LAYER_TILE_COLLISION;
    toolExtMask = Layer::LAYER_EXT_COLL | Layer::LAYER_EXT_COLLTYPE | Layer::LAYER_EXT_UNUSED | Layer::LAYER_EXT_ANIMON | Layer::LAYER_EXT_ANIMREV;
    toolFxMask = Layer::LAYER_FX_UNDEFINED;
    
    mousePointer();
    dragTool(1);
}


void SceneEditLayer::modifyTool() { start_func
    if (toolActive) {
        toolCtrl = SDL_GetModState() & KMOD_CTRL;
        toolAlt = SDL_GetModState() & KMOD_ALT;

        // Selection tools need entire area dirtied because this may cause
        // a deleted selection to reappear
        if ((toolActive == TOOLS_SELECT) || (toolActive == TOOLS_SELECTELLIPSE) || (toolActive == TOOLS_WAND)) {
            setDirty(1);
        }

        dragTool();
    }

    // Always update mouse pointer even if no active tool
    mousePointer();
}

void SceneEditLayer::dragTool(int firstTime, int lastTime) { start_func
    if (toolActive) {
        int rX, rY;
        int slope1, slope2;
        Rect rect;
        Rect layerBound = { 0, 0, layerWidth, layerHeight };
        int nonDraw = 0;
        if ((toolActive == TOOLS_SELECT) || (toolActive == TOOLS_SELECTELLIPSE) ||
            (toolActive == TOOLS_DROPPER) || (toolActive == TOOLS_WAND) ||
            (toolActive == TOOLS_SELECTDRAG) || (toolActive == TOOLS_SELECTITEMDRAG) || 
            (toolActive == TOOLS_PLACEITEM) || (toolActive == TOOLS_PLACESERIES) ||
            (toolActive == TOOLS_SELECTITEM)) {
            nonDraw = 1;
        }

        // So that undo warning boxes don't cancel us!
        int tool = toolActive;
        if (lastTime) toolActive = 0;
    
        try {
            switch (tool) {
                case TOOLS_SELECTITEMDRAG:
                    setDirtySelectedSpawns();
                    if (lastTime) {
                        // For undo
                        moveSpawnSelection(toolStartX - toolLastX, toolStartY - toolLastY);
                        world->undo.preUndoBlock();
                        moveSpawnSelection(cursorSpriteX - toolStartX, cursorSpriteY - toolStartY, 0);
                        world->undo.postUndoBlock();
                        
                    }
                    else if (!firstTime) {
                        moveSpawnSelection(cursorSpriteX - toolLastX, cursorSpriteY - toolLastY);
                    }
                    setDirtySelectedSpawns();
                    break;
                    
                case TOOLS_SELECTITEM:  
                    // Mark old range/rect dirty      
                    setDirtyPixelBox(toolStartX, toolStartY, toolLastX, toolLastY);
                    setDirtySelectedSpawns();
                    
                    // Keep a backup copy of old selection
                    if (firstTime) {
                        backupSelectedSpawns = selectedSpawns;
                        backupSelectedSpawnRect = selectedSpawnRect;
                    }
                    // Refresh from backup if dragging with ctrl/alt or last time (for undo)
                    else if ((toolCtrl) || (toolAlt) || (lastTime)) {
                        selectedSpawns = backupSelectedSpawns;
                        selectedSpawnRect = backupSelectedSpawnRect;
                    }
    
                    // Undo?
                    if (lastTime) {
                        if ((!toolCtrl) && (!toolAlt)) {
                            // Clear it AND store undo
                            clearSpawnSelection();
                        }
                        else {
                            // Just store undo
                            undoStoreSpawnSelect();
                        }
                    }
                    // Clear selection if no ctrl/alt
                    else if ((!toolCtrl) && (!toolAlt)) clearSpawnSelection(0);               
                    
                    // Select
                    selectSpawnRect(toolStartX, toolStartY, cursorSpriteX, cursorSpriteY, toolAlt ? 0 : 1);
                    recheckSpawnSelection();
                    
                    // If first time, no modifiers, and something was selected- switch to drag now
                    if ((firstTime) && (!toolCtrl) && (!toolAlt) && (selectedSpawnRect.w)) {
                        // Handles undo by running through right now as lasttime
                        finishTool();
                        startToolSpawnSelectionDrag();
                        return;
                    }
                    
                    // Mark new range dirty
                    setDirtyPixelBox(toolStartX, toolStartY, cursorSpriteX, cursorSpriteY);
                    setDirtySelectedSpawns();
                    
                    // Allow copy now?
                    if (lastTime) updateCopyTool();
                    break;
                
                case TOOLS_PLACEITEM:
                case TOOLS_PLACESERIES:
                    if (lastTime) {
                        // Position it
                        spawnPlace->setPos(cursorSpriteX, cursorSpriteY, 1);
                        
                        // Add sprite
                        // @TODO: Catch undo (and delete newspawn if needed)
                        // @TODO: load from currently selected sprite/object/dialog
                        
                        // Create
                        LayerEdit* layer = scene->getLayerEdit(cursorLayer);
                        SpawnEdit* newSpawn = new SpawnEdit(spawnPlace, layer, world->unusedSpawnId());
                        newSpawn->markLock();
                        
                        // If properties done, shortcut dialog for now
                        if ((spawnPropertiesDone) || (newSpawn->propertiesDialog())) {
                            // Place on layer- this is the undo point
                            newSpawn->setUndoReady();
                            layer->addSpawn(newSpawn, myFrame, this); // @TODO: can throw_File / Undo
                            newSpawn->markUnlock(); // Layer has it locked now
                            
                            // Remember settings for next placement
                            delete spawnPlace;
                            spawnPlace = new SpawnEdit(newSpawn, NULL);
                            spawnPlace->markLock();
                            spawnPropertiesDone = 1;
                        }
                        else {
                            delete newSpawn;
                        }
                        
                        // @TODO: Dirty sprite area only
                        setDirty(1);
                        
                        if ((tool == TOOLS_PLACEITEM) && (toolL == TOOLS_PLACEITEM)) {
                            desktop->broadcastEvent(SDL_COMMAND, TOOLS_SELECTITEM);
                            desktop->broadcastEvent(SDL_COMMAND, CMD_RELEASE);
                        }
                    }
                    break;
                    
                case TOOLS_SELECTDRAG:
                    // @TODO: Can this use moveselection()?
                    // Dirty current selection area
                    setDirtyBox(selectionRect.x + selectionXOffs, selectionRect.y + selectionYOffs,
                                selectionRect.x + selectionRect.w - 1 + selectionXOffs,
                                selectionRect.y + selectionRect.h - 1 + selectionYOffs, 1);
                
                    // Float selection?
                    if (firstTime) {
                        floatSelection();
                    }
                    else {
                        // Move selection
                        selectionXOffs += virtualCursorX - toolLastX;
                        selectionYOffs += virtualCursorY - toolLastY;
                        
                        // Dirty new area also
                        setDirtyBox(selectionRect.x + selectionXOffs, selectionRect.y + selectionYOffs,
                                    selectionRect.x + selectionRect.w - 1 + selectionXOffs,
                                    selectionRect.y + selectionRect.h - 1 + selectionYOffs, 1);
                    }
                    break;
                    
                case TOOLS_WAND:
                case TOOLS_SELECT:
                case TOOLS_SELECTELLIPSE:
                    // Keep a backup copy of old selection
                    if (firstTime) {
                        memcpy(backupSelected, selected, selectedPitch * layerHeight);
                        backupSelectionRect = selectionRect;
                    }
                    // Refresh from backup if dragging with ctrl/alt
                    else if ((toolCtrl) || (toolAlt)) {
                        memcpy(selected, backupSelected, selectedPitch * layerHeight);
                        selectionRect = backupSelectionRect;
                    }
                    
                    if (tool == TOOLS_WAND) {
                        // We get rectangle from previous time
                        if (!firstTime) {
                            rect.x = toolMinX;
                            rect.y = toolMinY;
                            rect.w = toolMaxX - toolMinX + 1;
                            rect.h = toolMaxY - toolMinY + 1;
    
                            // Mark previous rect dirty
                            setDirtyRect(rect, 1);
                        }
                        else {
                            rect.w = 0;
                        }
                    }
                    else if (tool == TOOLS_SELECT) {
                        rect = createRect(toolStartX, toolStartY, virtualCursorX, virtualCursorY);
                    }
                    else {
                        rX = abs(toolStartX - virtualCursorX);
                        rY = abs(toolStartY - virtualCursorY);
                    
                        // Special case
                        if (rX < 1) rX = 1;
                        if (rY < 1) rY = 1;
    
                        rect = createRect(toolStartX - rX, toolStartY - rY, toolStartX + rX, toolStartY + rY);
                    }
    
                    // We can't have a selection rect that's bigger than the selection surface
                    intersectRects(rect, layerBound);
    
                    // Undo?
                    if (lastTime) {
                        // If we're clearing selection, we must do extra work here
                        if ((!toolCtrl) && (!toolAlt)) {
                            // Refresh from backup
                            memcpy(selected, backupSelected, selectedPitch * layerHeight);
                            selectionRect = backupSelectionRect;
                            // Add into rectangle the area we're selecting
                            boundRects(selectionRect, rect);
                            // Clear it AND store undo
                            clearSelection();
                        }
                        else {
                            // Just undo the area we're adding/deleting from
                            if (rect.w) undoStoreSelect(rect.x, rect.y, rect.w, rect.h);
                        }
                    }
                    // Clear selection if no ctrl/alt
                    else if ((!toolCtrl) && (!toolAlt)) clearSelection(0);                
                    
                    // Draw
                    if (tool == TOOLS_WAND) {
                        // @TODO:
//                        if (!toolContiguous) { start_func
//                            rect = floodFillNonContiguous32(tile, selection, virtualCursorX, virtualCursorY, mapColor32(0, 0, 0, toolAlt ? 0 : 255), toolTolerance);
//                        }
//                        else {
//                            // Use alpha workspace as temp area, use 255/255/255 as filler color
//                            drawRect(0, 0, alphaWorkspace->w, alphaWorkspace->h, mapColor32(255, 255, 255, 255), alphaWorkspace);
//                            rect = floodFill32(tile, alphaWorkspace, virtualCursorX, virtualCursorY, mapColor32(0, 0, 0, toolAlt ? 0 : 255), toolTolerance);
//                            SDL_SetAlpha(alphaWorkspace, 0, 255);
//                            SDL_SetColorKey(alphaWorkspace, SDL_SRCCOLORKEY, mapColor32(255, 255, 255, 255));
//                            blit(0, 0, alphaWorkspace, 0, 0, selection, tileWidth, tileHeight);
//                            SDL_SetColorKey(alphaWorkspace, 0, 0);
//                            SDL_SetAlpha(alphaWorkspace, SDL_SRCALPHA, 255);
//                        }
    
                        // Remember min/max for undo next time
                        if (rect.w) {
                            toolMinX = rect.x;
                            toolMinY = rect.y;
                            toolMaxX = rect.x + rect.w - 1;
                            toolMaxY = rect.y + rect.h - 1;
                        }
                        else {
                            // Causes no undo area or undirty to occur next frame
                            toolMinX = toolMaxX = -1;
                        }
                    }
                    else if (tool == TOOLS_SELECT) {
                        selectRect(toolStartX, toolStartY, virtualCursorX, virtualCursorY, toolAlt ? 0 : 1);
                    }
                    else {
                        selectEllipse(toolStartX, toolStartY, rX, rY, toolAlt ? 0 : 1);
                    }
                    setDirtyRect(rect, 1);
    
                    // Add to overall selection bounding box
                    if (!toolAlt) boundRects(selectionRect, rect);
                    // ...or fix selection rectangle if removing stuff
                    else fixSelectionRect();
    
                    // Previous
                    if (!firstTime) {
                        if (tool == TOOLS_WAND) {
                            // (was dirtied above, at beginning)
                        }
                        else if (tool == TOOLS_SELECT) {
                            setDirtyBox(toolStartX, toolStartY, toolLastX, toolLastY, 1);
                        }
                        else {
                            rX = abs(toolStartX - toolLastX);
                            rY = abs(toolStartY - toolLastY);
                            if (rX < 1) rX = 1;
                            if (rY < 1) rY = 1;
                            setDirtyBox(toolStartX - rX, toolStartY - rY, toolStartX + rX, toolStartY + rY, 1);
                        }
                    }
                    
                    // Allow copy now?
                    if (lastTime) updateCopyTool();
                    break;
    
                case TOOLS_DROPPER:
                    // @TODO:
                    break;
            
                case TOOLS_PEN:
                    if (!lastTime) {
                        layerDrawLine(toolStartX, toolStartY, virtualCursorX, virtualCursorY, layerEdit[cursorLayer][0], toolDataMask, toolData);
                        if (layerEdit[cursorLayer][1]) layerDrawLine(toolStartX, toolStartY, virtualCursorX, virtualCursorY, layerEdit[cursorLayer][1], toolExtMask, toolExt);
                        if (layerEdit[cursorLayer][2]) layerDrawLine(toolStartX, toolStartY, virtualCursorX, virtualCursorY, layerEdit[cursorLayer][2], toolFxMask, toolFx);
                        setDirtyBox(toolStartX, toolStartY, virtualCursorX, virtualCursorY);
                    }
                    
                    // Track area covered for undo
                    if (virtualCursorX < toolMinX) toolMinX = virtualCursorX;
                    if (virtualCursorX > toolMaxX) toolMaxX = virtualCursorX;
                    if (virtualCursorY < toolMinY) toolMinY = virtualCursorY;
                    if (virtualCursorY > toolMaxY) toolMaxY = virtualCursorY;
                    if (toolStartX < toolMinX) toolMinX = toolStartX;
                    if (toolStartX > toolMaxX) toolMaxX = toolStartX;
                    if (toolStartY < toolMinY) toolMinY = toolStartY;
                    if (toolStartY > toolMaxY) toolMaxY = toolStartY;
    
                    toolStartX = virtualCursorX;
                    toolStartY = virtualCursorY;
    
                    if (lastTime) undoStoreLayerBox(toolMinX, toolMinY, toolMaxX, toolMaxY);
                    else if (tools) ; // @TODO: preview redraw
                    break;
                    
                case TOOLS_FILL:
                    // @TODO: advanced option to specify mask of what to check fill against
                    
                    // Undo based on rectangle from last time
                    if (lastTime) undoStoreLayerBox(toolMinX, toolMinY, toolMaxX, toolMaxY);
    
                    // Dirty previous rectangle
                    if (!firstTime) {
                        refreshData(1, toolMinX, toolMinY, toolMaxX, toolMaxY);
                        setDirtyBox(toolMinX, toolMinY, toolMaxX, toolMaxY);
                    }
                    
                    // Flood fill
                    // @TODO: doesn't support layer [2] (effects data)
                    if (!toolContiguous) {
                        if (layerEdit[cursorLayer][1]) rect = layerMatchFill(virtualCursorX, virtualCursorY, Layer::LAYER_TILE_FILL_MASK, layerEdit[cursorLayer][0], toolDataMask, toolData, layerEdit[cursorLayer][1], toolExtMask, toolExt);
                        else rect = layerMatchFill(virtualCursorX, virtualCursorY, Layer::LAYER_TILE_FILL_MASK, layerEdit[cursorLayer][0], toolDataMask, toolData);
                    }
                    else {
                        if (layerEdit[cursorLayer][1]) rect = layerFloodFill(virtualCursorX, virtualCursorY, Layer::LAYER_TILE_FILL_MASK, layerEdit[cursorLayer][0], toolDataMask, toolData, layerEdit[cursorLayer][1], toolExtMask, toolExt);
                        else rect = layerFloodFill(virtualCursorX, virtualCursorY, Layer::LAYER_TILE_FILL_MASK, layerEdit[cursorLayer][0], toolDataMask, toolData);
                    }
                    setDirtyRect(rect);
                    
                    // Remember min/max for undo on last time
                    if (rect.w) {
                        toolMinX = rect.x;
                        toolMinY = rect.y;
                        toolMaxX = rect.x + rect.w - 1;
                        toolMaxY = rect.y + rect.h - 1;
                    }
                    else {
                        // Causes no undo area or undirty to occur next frame
                        toolMinX = toolMaxX = -1;
                    }
                    break;
                    
                case TOOLS_LINE:
                case TOOLS_RECT:
                case TOOLS_RECTFILL:
                    if (lastTime) undoStoreLayerBox(toolStartX, toolStartY, virtualCursorX, virtualCursorY);
                    
                    // Reload from last time, not entire layer
                    if (!firstTime) refreshData(1, toolStartX, toolStartY, toolLastX, toolLastY);
    
                    rX = virtualCursorX;
                    rY = virtualCursorY;
                    
                    // Limit to square or straight line?
                    if (toolCtrl) {
                        if (tool == TOOLS_LINE) {
                            // Determine approximate slope of line
                            slope1 = abs(toolStartX - rX);
                            slope2 = abs(toolStartY - rY);
                            // (we only care, if both sizes are > 0)
                            if ((slope1) && (slope2)) {
                                if (slope1 > slope2) swap(slope1, slope2);
                                // slope1/slope2 will be a fraction between 0 (flat) and
                                // 1 (diagonal of 45deg multiple); cutoff point is 0.5
                                if (slope1 * 2 / slope2 >= 1) {
                                    // Square
                                    if (abs(toolStartX - rX) < abs(toolStartY - rY)) {
                                        rY = toolStartY + abs(toolStartX - rX) * (toolStartY < rY ? 1 : -1);
                                    }
                                    else {
                                        rX = toolStartX + abs(toolStartY - rY) * (toolStartX < rX ? 1 : -1);
                                    }
                                }
                                else {
                                    // Flat line
                                    if (abs(toolStartX - rX) < abs(toolStartY - rY)) {
                                        rX = toolStartX;
                                    }
                                    else {
                                        rY = toolStartY;
                                    }
                                }
                            }
                        }
                        else {
                            // Square
                            if (abs(toolStartX - rX) < abs(toolStartY - rY)) {
                                rY = toolStartY + abs(toolStartX - rX) * (toolStartY < rY ? 1 : -1);
                            }
                            else {
                                rX = toolStartX + abs(toolStartY - rY) * (toolStartX < rX ? 1 : -1);
                            }
                        }
                    }
                    
                    if (tool == TOOLS_LINE) {
                        layerDrawLine(toolStartX, toolStartY, rX, rY, layerEdit[cursorLayer][0], toolDataMask, toolData);
                        if (layerEdit[cursorLayer][1]) layerDrawLine(toolStartX, toolStartY, rX, rY, layerEdit[cursorLayer][1], toolExtMask, toolExt);
                        if (layerEdit[cursorLayer][2]) layerDrawLine(toolStartX, toolStartY, rX, rY, layerEdit[cursorLayer][2], toolFxMask, toolFx);
                    }
                    else if (tool == TOOLS_RECT) {
                        layerDrawBox(toolStartX, toolStartY, rX, rY, layerEdit[cursorLayer][0], toolDataMask, toolData);
                        if (layerEdit[cursorLayer][1]) layerDrawBox(toolStartX, toolStartY, rX, rY, layerEdit[cursorLayer][1], toolExtMask, toolExt);
                        if (layerEdit[cursorLayer][2]) layerDrawBox(toolStartX, toolStartY, rX, rY, layerEdit[cursorLayer][2], toolFxMask, toolFx);
                    }
                    else {
                        layerDrawRect(toolStartX, toolStartY, rX, rY, layerEdit[cursorLayer][0], toolDataMask, toolData);
                        if (layerEdit[cursorLayer][1]) layerDrawRect(toolStartX, toolStartY, rX, rY, layerEdit[cursorLayer][1], toolExtMask, toolExt);
                        if (layerEdit[cursorLayer][2]) layerDrawRect(toolStartX, toolStartY, rX, rY, layerEdit[cursorLayer][2], toolFxMask, toolFx);
                    }
                    setDirtyBox(toolStartX, toolStartY, rX, rY);
                    // (no need to limit based on toolCtrl, as a limited area will always
                    //  be smaller than this area we use here)
                    if (!firstTime) setDirtyBox(toolStartX, toolStartY, toolLastX, toolLastY);
                    break;
                    
                case TOOLS_ELLIPSE:
                case TOOLS_ELLIPSEFILL:
                    rX = abs(toolStartX - virtualCursorX);
                    rY = abs(toolStartY - virtualCursorY);
                
                    // Special case
                    if (rX < 1) rX = 1;
                    if (rY < 1) rY = 1;
                    
                    // Circle?
                    if (toolCtrl) {
                        rX = min(rX, rY);
                        rY = rX;
                    }
    
                    if (lastTime) undoStoreLayerBox(toolStartX - rX, toolStartY - rY, toolStartX + rX, toolStartY + rY);
                    // reload from area affected last time, not entire layer
                    int prX, prY;
                    if (!firstTime) {
                        prX = abs(toolStartX - toolLastX);
                        prY = abs(toolStartY - toolLastY);
                        if (prX < 1) prX = 1;
                        if (prY < 1) prY = 1;
                        // (no need to limit to a circle based on toolCtrl, this will always
                        // cover the minimum area needed)
                        refreshData(1, toolStartX - prX, toolStartY - prY, toolStartX + prX, toolStartY + prY);
                    }
    
                    if (tool == TOOLS_ELLIPSE) {
                        layerDrawEllipse(toolStartX, toolStartY, rX, rY, layerEdit[cursorLayer][0], toolDataMask, toolData);
                        if (layerEdit[cursorLayer][1]) layerDrawEllipse(toolStartX, toolStartY, rX, rY, layerEdit[cursorLayer][1], toolExtMask, toolExt);
                        if (layerEdit[cursorLayer][2]) layerDrawEllipse(toolStartX, toolStartY, rX, rY, layerEdit[cursorLayer][2], toolFxMask, toolFx);
                    }
                    else {
                        layerDrawEllipseFill(toolStartX, toolStartY, rX, rY, layerEdit[cursorLayer][0], toolDataMask, toolData);
                        if (layerEdit[cursorLayer][1]) layerDrawEllipseFill(toolStartX, toolStartY, rX, rY, layerEdit[cursorLayer][1], toolExtMask, toolExt);
                        if (layerEdit[cursorLayer][2]) layerDrawEllipseFill(toolStartX, toolStartY, rX, rY, layerEdit[cursorLayer][2], toolFxMask, toolFx);
                    }
                    
                    setDirtyBox(toolStartX - rX, toolStartY - rY, toolStartX + rX, toolStartY + rY);
    
                    // Previous
                    if (!firstTime) {
                        setDirtyBox(toolStartX - prX, toolStartY - prY, toolStartX + prX, toolStartY + prY);
                    }
                    break;
            }
        }
        catch (UndoException& e) {
            toolActive = tool;
            cancelTool();
            return;
        }
        
        if ((lastTime) && (!nonDraw)) {
            // @TODO: only apply a section (to reduce overhead on preview updates)
            applyData(1);
        }
        else if (!nonDraw) ; // @TODO: preview redraw
        
        if (spriteMode) {
            toolLastX = cursorSpriteX;
            toolLastY = cursorSpriteY;        
        }
        else {
            toolLastX = virtualCursorX;
            toolLastY = virtualCursorY;        
        }
    }
}

void SceneEditLayer::cancelTool() { start_func
    startToolOnMove = 0;
    if (toolActive) {
        int rX;
        int rY;
    
        switch (toolActive) {
            case TOOLS_SELECT:
            case TOOLS_SELECTELLIPSE:
            case TOOLS_WAND:
                // Refresh from backup, dirty all
                memcpy(selected, backupSelected, selectedPitch * layerHeight);
                backupSelectionRect = selectionRect;
                setDirty(1);
                break;

            case TOOLS_PEN:
            case TOOLS_FILL:
                refreshData(1, toolMinX, toolMinY, toolMaxX, toolMaxY);
                // @TODO: preview redraw
                setDirtyBox(toolMinX, toolMinY, toolMaxX, toolMaxY);
                break;
                
            case TOOLS_LINE:
            case TOOLS_RECT:
            case TOOLS_RECTFILL:
                refreshData(1, toolStartX, toolStartY, toolLastX, toolLastY);
                // @TODO: preview redraw
                setDirtyBox(toolStartX, toolStartY, toolLastX, toolLastY);
                break;
                
            case TOOLS_ELLIPSE:
            case TOOLS_ELLIPSEFILL:
                rX = abs(toolStartX - toolLastX);
                rY = abs(toolStartY - toolLastY);
                if (rX < 1) rX = 1;
                if (rY < 1) rY = 1;
                refreshData(1, toolStartX - rX, toolStartY - rY, toolStartX + rX, toolStartY + rY);
                // @TODO: preview redraw
                setDirtyBox(toolStartX - rX, toolStartY - rY, toolStartX + rX, toolStartY + rY);
                break;
        }
    
        mousePointer();
        toolActive = 0;
    }
}

void SceneEditLayer::finishTool() { start_func
    startToolOnMove = 0;
    if (toolActive) {
        dragTool(0, 1);
        mousePointer();
        // toolActive = 0 taken care of by dragtool
    }
}

void SceneEditLayer::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
    assert(destSurface);

    if (visible) {
        // If dirty, redraw range or all
        if (dirty) {
            if (totalDirty) {
                // Range should include entire area, so we cover "outside" current
                // layer if appropriate
                toDisplay = clipArea;
            }
            else {
                dirtyRange.x += x + xOffset;
                dirtyRange.y += y + yOffset;
                // Range must include requested update area as well
                boundRects(toDisplay, dirtyRange);
            }
            dirty = totalDirty = 0;
            dirtyRange.w = 0;
            intersectRects(toDisplay, clipArea);
        }
        
        xOffset += x;
        yOffset += y;
        
        // Anything to draw?
        if (toDisplay.w) {
            SDL_SetClipRect(destSurface, &toDisplay);
            SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_BKFILL]);
            
            // Draw all visible layers, bottom up
            int pos = numLayers - 1;
            Uint32 bit = 1 << pos;
            for (; pos >= 0; --pos, bit >>= 1) {
                if (layersView & bit) {
                    int dim = layersDim & bit;
                    if (pos == cursorLayer) dim = 0;
                    
                    if (layersAffect & bit) {
                        // Draw directly from our data, or use our data as floating?
                        if (selectionMode == SELECTION_OUTLINE) {
                            LayerEdit::blitTileLayer(scene->getLayerEdit(pos)->getTileSetEdit(),
                                                     layerEdit[pos][0], layerEdit[pos][1],
                                                     layerWidth, layerHeight,
                                                     toDisplay.x - xOffset, toDisplay.y - yOffset,
                                                     toDisplay.w, toDisplay.h,
                                                     xOffset, yOffset, destSurface, dim);
                        }
                        else {
                            LayerEdit* layer = scene->getLayerEdit(pos);
                        
                            LayerEdit::blitTileLayer(layer->getTileSetEdit(),
                                                     layer->getTileData(), layer->getExtendedData(),
                                                     layerWidth, layerHeight,
                                                     toDisplay.x - xOffset, toDisplay.y - yOffset,
                                                     toDisplay.w, toDisplay.h,
                                                     xOffset, yOffset, destSurface, dim,
                                                     layerEdit[pos][0], layerEdit[pos][1],
                                                     selectionXOffs, selectionYOffs, layerWidth);
                        }
                    }
                    else {
                        scene->getLayerEdit(pos)->blit(toDisplay.x - xOffset, toDisplay.y - yOffset,
                                                       toDisplay.w, toDisplay.h,
                                                       xOffset, yOffset, destSurface, dim);
                    }
                    
                    // @TODO: separate dim determination for spawns
                    scene->getLayerEdit(pos)->blitSpawns(toDisplay.x - xOffset, toDisplay.y - yOffset,
                                                         toDisplay.w, toDisplay.h,
                                                         xOffset, yOffset, destSurface,
                                                         &selectedSpawns, dim ? 1 : 0);
                }
            }
            
            // Draw grid (top/left lines)
            if (enableGrid) {
                // Determine first/last rows to show
                int tY = max(0, (toDisplay.y - yOffset) / tileHeight);
                int bottom = (toDisplay.y + toDisplay.h - yOffset - 1) / tileHeight;
                if (bottom > layerHeight) bottom = layerHeight;
    
                // Determine first/last tiles of each row we need to show
                int tX = max(0, (toDisplay.x - xOffset) / tileWidth);
                int rightmost = (toDisplay.x + toDisplay.w - xOffset - 1) / tileWidth;
                if (rightmost > layerWidth) rightmost = layerWidth;
                
                // Determine the starting x/y pixel
                int dX = xOffset + tX * tileWidth;
                int dY = yOffset + tY * tileHeight;
                int fY = dY;
                
                // Determine length of lines
                // @TODO: displays one pixel too short in some corner cases
                int lineW = min((int)toDisplay.w, layerWidth * tileWidth - toDisplay.x + xOffset);
                int lineH = min((int)toDisplay.h, layerHeight * tileHeight - toDisplay.y + yOffset);

                // Horizontal lines
                if (lineW > 0) {
                    for (; tY <= bottom; ++tY) {
                        drawHLine(dX, toDisplay.x + lineW, dY, guiPacked[COLOR_GRID], destSurface);
                        dY += tileHeight;
                    }
                }
                
                // Vertical lines
                if (lineH > 0) {
                    for (; tX <= rightmost; ++tX) {
                        drawVLine(fY, toDisplay.y + lineH, dX, guiPacked[COLOR_GRID], destSurface);
                        dX += tileWidth;
                    }
                }
            }
            
            // Draw cursor (@TODO: cursors>1 combined with a centered client within frame may allow cursor to go off edges- ok?)
            if (partialFocus) {
                int cX = xOffset + cursorX * tileWidth;
                int cY = yOffset + cursorY * tileHeight;
                drawSelectRect(cX, cY, cursorW * tileWidth, cursorH * tileHeight,
                               guiPacked[haveFocus ? COLOR_TILECURSOR : COLOR_TILESELECTION], destSurface,
                               haveFocus ? desktop->currentCursorAlpha() : 128);
                drawBox(cX, cY, cursorW * tileWidth, cursorH * tileHeight, guiPacked[COLOR_TILECURSORBORDER1], destSurface);
                drawBox(cX + 1, cY + 1, cursorW * tileWidth - 2, cursorH * tileHeight - 2, guiPacked[COLOR_TILECURSORBORDER2], destSurface);
            }

            // Now handle selection borders
            if ((partialFocus) && (selectionRect.w)) {
                // Determine first/last rows to show
                int tY = max(0, (toDisplay.y - yOffset) / tileHeight);
                int bottom = (toDisplay.y + toDisplay.h - yOffset - 1) / tileHeight;
                if (bottom >= layerHeight) bottom = layerHeight - 1;
    
                // Determine first/last tiles of each row we need to show
                int leftmost = max(0, (toDisplay.x - xOffset) / tileWidth);
                int rightmost = (toDisplay.x + toDisplay.w - xOffset - 1) / tileWidth;
                if (rightmost >= layerWidth) rightmost = layerWidth - 1;
                
                // Clip to selection boundaries
                if (tY < selectionRect.y + selectionYOffs) tY = selectionRect.y + selectionYOffs;
                if (bottom >= selectionRect.y + selectionRect.h + selectionYOffs) bottom = selectionRect.y + selectionRect.h + selectionYOffs - 1;
                if (leftmost < selectionRect.x + selectionXOffs) leftmost = selectionRect.x + selectionXOffs;
                if (rightmost >= selectionRect.x + selectionRect.w + selectionXOffs) rightmost = selectionRect.x + selectionRect.w + selectionXOffs - 1;
                
                // Determine the starting x/y pixel
                int dX = xOffset + leftmost * tileWidth;
                int dY = yOffset + tY * tileHeight;
                
                for (; tY <= bottom; ++tY) {
                    // Place in bitarray terms
                    Uint8 bit = 1 << ((leftmost - selectionXOffs) & 7);
                    Uint8* selData = selected + ((leftmost - selectionXOffs) >> 3) + (tY - selectionYOffs) * selectedPitch;

                    Uint8 prevbit = bit >> 1;
                    Uint8* prevData = selData;
                    if (prevbit == 0) {
                        prevbit = 128;
                        --prevData;
                    }

                    Uint8 nextbit = bit << 1;
                    Uint8* nextData = selData;
                    if (nextbit == 0) {
                        nextbit = 1;
                        ++nextData;
                    }
                    
                    for (int tX = leftmost; tX <= rightmost; ++tX) {
                        if (*selData & bit) {
                            // Don't highlight if part of cursor
                            if ((tX < cursorX) || (tX >= cursorX + cursorW) ||
                                (tY < cursorY) || (tY >= cursorY + cursorH)) {
                                drawSelectRect(dX, dY, tileWidth, tileHeight,
                                               guiPacked[COLOR_TILESELECTION], destSurface);
                            }
                                           
                            // Draw ants borders
                            int drawT = 1;
                            int drawB = 1;
                            int drawL = 1;
                            int drawR = 1;
                        
                            // Determine which borders to hide
                            if ((tY > selectionYOffs) && (*(selData - selectedPitch) & bit)) drawT = 0;
                            if ((tY < (layerHeight + selectionYOffs - 1)) && (*(selData + selectedPitch) & bit)) drawB = 0;
                            if ((tX > selectionXOffs) && (*prevData & prevbit)) drawL = 0;
                            if ((tX < (layerWidth + selectionXOffs - 1)) && (*nextData & nextbit)) drawR = 0;
                            
                            if (drawT) drawAntsHLine(dX, dX + tileWidth - 1, dY, desktop->currentSelectionPhase(), destSurface);
                            if (drawB) drawAntsHLine(dX, dX + tileWidth - 1, dY + tileHeight - 1, desktop->currentSelectionPhase(), destSurface);
                            if (drawL) drawAntsVLine(dY, dY + tileHeight - 1, dX, desktop->currentSelectionPhase(), destSurface);
                            if (drawR) drawAntsVLine(dY, dY + tileHeight - 1, dX + tileWidth - 1, desktop->currentSelectionPhase(), destSurface);
                        }
                        
                        prevbit = bit;
                        bit = nextbit;
                        
                        prevData = selData;
                        selData = nextData;
                        
                        nextbit <<= 1;
                        if (nextbit == 0) {
                            nextbit = 1;
                            ++nextData;
                        }
                        
                        dX += tileWidth;
                    }
                    
                    dX = xOffset + leftmost * tileWidth;
                    dY += tileHeight;
                }
            }

            // Draw sprite cursor?
            if (spriteShown) {
                spawnPlace->setPos(spriteShownX, spriteShownY, 1);
                spawnPlace->blit(xOffset, yOffset, destSurface, toDisplay.x - xOffset,
                                 toDisplay.y - yOffset, toDisplay.w, toDisplay.h, 2);
            }
            
            // Sprite selection box?
            if (toolActive == TOOLS_SELECTITEM) {
                Rect selArea = createRect(toolStartX, toolStartY, toolLastX, toolLastY);
                drawSelectRect(selArea.x + xOffset, selArea.y + yOffset,
                               selArea.w, selArea.h,
                               guiPacked[COLOR_SELECTION1], destSurface);
                drawAntsBox(selArea.x + xOffset, selArea.y + yOffset,
                            selArea.w, selArea.h,
                            desktop->currentSelectionPhase(), destSurface);
            }
        }
    }
}

void SceneEditLayer::undoStoreLayerBox(int x1, int y1, int x2, int y2, int cursorLayerOnly) throw_Undo { start_func
    if (x1 > x2) swap(x1, x2);
    if (y1 > y2) swap(y1, y2);
    undoStoreLayer(x1, y1, x2 - x1 + 1, y2 - y1 + 1, cursorLayerOnly);
}

void SceneEditLayer::undoStoreLayer(int x, int y, int w, int h, int cursorLayerOnly) throw_Undo { start_func
    Uint32 bit = 1;
    int pos = 0;
    int max = numLayers - 1;
    if (cursorLayerOnly) {
        pos = max = cursorLayer;
        if (pos) bit <<= pos;
    }
    world->undo.preUndoBlock();
    for (; pos <= max; ++pos, bit <<= 1) {
        if (layersAffect & bit) {
            world->undo.storeUndoLayerTile(scene->getLayer(pos)->getId(), x, y, w, h, myFrame);
        }
    }
    world->undo.postUndoBlock();
}

void SceneEditLayer::undoStoreSelect(int x, int y, int w, int h, int forceStoreData) throw_Undo { start_func
    // we have to ensure x/y/w/h are within range ourselves
    if (x < 0) x = 0;
    if (y < 0) y = 0;
    if (x + w >= layerWidth) w = layerWidth - x - 1;
    if (y + h >= layerHeight) h = layerHeight - y - 1;
    
    world->undo.preUndoBlock();
    world->undo.storeUndoLayerTileCur(layersAffect, layersView, layersDim, myFrame);
    
    // Before data, if not floating
    if (selectionMode == SELECTION_OUTLINE) {
        world->undo.storeUndoLayerTileSelection(&selected, x, y, w, h, selectedPitch, selectionXOffs, selectionYOffs, selectionMode, myFrame);
    }
    
    // Store data too, if not an outline
    if ((selectionMode != SELECTION_OUTLINE) || (forceStoreData)) {
        Uint32 bit = 1;
        for (int pos = 0; pos < numLayers; ++pos, bit <<= 1) {
            if (layersAffect & bit) {
                world->undo.storeUndoLayerTileTemp(&layerEdit[pos][0], &layerEdit[pos][1], &layerEdit[pos][2], x, y, w, h, layerWidth, myFrame);
            }
        }
    }

    // After data, if floating
    if (selectionMode != SELECTION_OUTLINE) {
        world->undo.storeUndoLayerTileSelection(&selected, x, y, w, h, selectedPitch, selectionXOffs, selectionYOffs, selectionMode, myFrame);
    }

    world->undo.storeUndoLayerTileCur(layersAffect, layersView, layersDim, myFrame);
    world->undo.postUndoBlock();
}

void SceneEditLayer::clearSelection(int storeUndo) throw_Undo { start_func
    assert(selected);

    if (selectionRect.w) {
        if (storeUndo) undoStoreSelect(selectionRect.x, selectionRect.y, selectionRect.w, selectionRect.h);
        
        memset(selected, 0, selectedPitch * layerHeight);
        
        // If a floating selection, reload data
        if (selectionMode != SELECTION_OUTLINE) refreshData(0);

        // Add in offset so proper area is dirtied
        selectionRect.x += selectionXOffs;
        selectionRect.y += selectionYOffs;
        setDirtyRect(selectionRect);
        
        selectionRect.w = 0;
        selectionXOffs = selectionYOffs = 0;
        selectionMode = SELECTION_OUTLINE;
    }
    
    updateCopyTool();
}

void SceneEditLayer::floatSelection(int copyPrep, int delSel) throw_Undo { start_func
    if ((selectionMode == SELECTION_OUTLINE) && (selectionRect.w)) {
        if (!copyPrep) {
            world->undo.preUndoBlock();
            undoStoreLayer(selectionRect.x, selectionRect.y, selectionRect.w, selectionRect.h, 0);
            undoStoreSelect(selectionRect.x, selectionRect.y, selectionRect.w, selectionRect.h, 1);
            world->undo.postUndoBlock();
        }

        // (set dirty first, in case rect resizes)
        setDirtyRect(selectionRect);
        
        // Cut out selection on each layer
        Uint32* tempFloat = NULL;
        Uint32* tempFloatExt = NULL;
        Uint32* tempFloatFx = NULL;
        
        Rect affect = { 0, 0, layerWidth, layerHeight };
        int affectBit = 1;
        int layerSize = layerWidth * layerHeight;
        for (int pos = 0; pos < numLayers; ++pos, affectBit <<= 1) {
            if (layersAffect & affectBit) {
                // Allocate temporary storage to float selection to
                tempFloat = new Uint32[layerSize];
                memSet32(tempFloat, Layer::LAYER_TILE_DEFAULT, layerSize);
                if (layerEdit[pos][1]) {
                    tempFloatExt = new Uint32[layerSize];
                    memSet32(tempFloatExt, Layer::LAYER_EXT_DEFAULT, layerSize);
                }
                if (layerEdit[pos][2]) {
                    tempFloatFx = new Uint32[layerSize];
                    memSet32(tempFloatFx, Layer::LAYER_FX_DEFAULT, layerSize);
                }

                // Scan layer and move selected data that isn't tile 0
                Uint8 bit = 1;
                Uint8* source = selected;
                Uint32* src = layerEdit[pos][0];
                Uint32* srcExt = layerEdit[pos][1];
                Uint32* srcFx = layerEdit[pos][2];
                Uint32* dest = tempFloat;
                Uint32* destExt = tempFloatExt;
                Uint32* destFx = tempFloatFx;
                for (int y = 0; y < layerHeight; ++y) {
                    for (int x = 0; x < layerWidth; ++x) {
                        if (((*src & Layer::LAYER_TILE_INDEX) != 0) && (*source & bit)) {
                            *dest = *src;
                            *src = Layer::LAYER_TILE_DEFAULT;
                            if (srcExt) {
                                *destExt = *srcExt;
                                *srcExt = Layer::LAYER_EXT_DEFAULT;
                            }
                            if (srcFx) {
                                *destFx = *srcFx;
                                *srcFx = Layer::LAYER_FX_DEFAULT;
                            }
                        }
                        ++src;
                        ++dest;
                        if (srcExt) {
                            ++srcExt;
                            ++destExt;
                        }
                        if (srcFx) {
                            ++srcFx;
                            ++destFx;
                        }
                        bit <<= 1;
                        if (bit == 0) {
                            bit = 1;
                            ++source;
                        }
                    }
                    // If bit just wrapped to 1, we have an even multiple
                    // otherwise, move forward the final byte
                    if (bit > 1) ++source;
                    bit = 1;
                }
                
                // Apply data
                if (!copyPrep) scene->getLayerEdit(pos)->saveLayer(layerEdit[pos][0], layerEdit[pos][1], layerEdit[pos][2], layerWidth, affect, this);
                
                // Replace our layer data
                if (delSel) {
                    delete[] tempFloat;
                    delete[] tempFloatExt;
                    delete[] tempFloatFx;
                }
                else {
                    delete[] layerEdit[pos][0];
                    delete[] layerEdit[pos][1];
                    delete[] layerEdit[pos][2];
                    layerEdit[pos][0] = tempFloat;
                    layerEdit[pos][1] = tempFloatExt;
                    layerEdit[pos][2] = tempFloatFx;
                }
                tempFloat = NULL;
                tempFloatExt = NULL;
                tempFloatFx = NULL;
            }
        }
        
        if (delSel) return;

        fixSelectionRect(1);
        selectionMode = SELECTION_OPAQUE;

        // (possible that there's no selection now)
        updateCopyTool();
    }
}

void SceneEditLayer::doneSelection() throw_Undo { start_func
    if (selectionMode == SELECTION_OUTLINE) clearSelection();
    else mergeSelection();
}

int SceneEditLayer::pasteSelection() throw_Undo { start_func
    if (canConvertClipboard(CLIPBOARD_SPAWN)) {
        int pNumSpawns;
        int pNumLayers;
        
        clipboardPasteSpawnInfo(&pNumSpawns, &pNumLayers);
        
        // Note affected layers- warn user for problems
        int affectBit = 1;
        int matchedLayers = 0;
        int pos = 0;
        map<int, int> layerMapping;
        // One layer of data = always on cursor layer
        if (pNumLayers == 1) {
            layerMapping[0] = cursorLayer;
            matchedLayers = 1;
        }
        else {
            for (; pos < numLayers; ++pos, affectBit <<= 1) {
                if (layersAffect & affectBit) {
                    layerMapping[matchedLayers] = pos;
                    ++matchedLayers;
                }
            }
        }
        
        // Too many layers?
        if (matchedLayers < pNumLayers) {
            if (!guiConfirmBox("Clipboard contains objects on more layers than marked for editing- continue?", "Layer Mismatch")) return 0;
        }
        
        // Determine base x/y-
        //   Start with mouse position
        //   Prefer cursor position if more recently used
        //   Prefer offset from current selection position if any
        //   Ensure upperleft corner is not further up than onscreen upper-left
        //   (move selection to be visible if too far offscreen to right/bottom)
        int baseX = pixelMouseX;
        int baseY = pixelMouseY;
        if (spriteAtCursor) {
            baseX = cursorX * tileWidth;
            baseY = cursorY * tileHeight;
        }
        if (selectedSpawnRect.w) {
            baseX = selectedSpawnRect.x + tileWidth;
            baseY = selectedSpawnRect.y + tileHeight;
        }
        while (baseX > -x + viewWidth - tileWidth) baseX -= tileWidth;
        while (baseY > -y + viewHeight - tileHeight) baseY -= tileHeight;
        while (baseX < -x) baseX += tileWidth;
        while (baseY < -y) baseY += tileHeight;
        
        // Start undo and clear current spawn selection (storing undo for that too)
        world->undo.preUndoBlock();
        setDirtySelectedSpawns();
        clearSpawnSelection(); // @TODO: can throw Undo (ok?)

        // Now actually paste spawn data
        for (pos = 0; pos < pNumSpawns; ++pos) {
            int lNum;
            SpawnEdit* newSpawn = new SpawnEdit(world, NULL, world->unusedSpawnId());
            clipboardPasteSpawn(pos, newSpawn, &lNum, baseX, baseY);
            LayerEdit* layer = scene->getLayerEdit(layerMapping[lNum]);
            newSpawn->setLayer(layer);
            newSpawn->setUndoReady();
            layer->addSpawn(newSpawn, myFrame, this); // @TODO: can throw_File / Undo
            selectedSpawns.insert(newSpawn->getId());
        }        
        
        // Finalize selection and undo
        recheckSpawnSelection();
        world->undo.postUndoBlock();
        updateCopyTool();
        setDirtySelectedSpawns();
        
        return TOOLS_SELECTITEM;
    }
    else if (canConvertClipboard(CLIPBOARD_LAYER)) {
        int pNumLayers;
        int pWidth;
        int pHeight;
        const void* pTileSet;
        int pHasExt;
        int pHasFx;
        int warnedTileSet = 0;
        int warnedExt = 0;
        int warnedFx = 0;
        
        clipboardPasteLayerInfo(&pNumLayers, &pWidth, &pHeight);
        
        // Match up against affected layers- warn user for problems
        int affectBit = 1;
        int matchedLayers = 0;
        int pos = 0;
        for (; pos < numLayers; ++pos, affectBit <<= 1) {
            if (layersAffect & affectBit) {
                clipboardPasteLayerInfoDetails(matchedLayers, &pTileSet, &pHasExt, &pHasFx);
                
                // Same tileset? (if one specified)
                if ((pTileSet) && (!warnedTileSet)) {
                    if (pTileSet != scene->getLayer(pos)->getTileSet()) {
                        if (!guiConfirmBox("Layer tileset may not match clipboard data- continue?", "Tileset Mismatch")) return 0;
                        warnedTileSet = 1;
                    }
                }

                // Effects data
                if ((pHasExt) && (!warnedExt)) {
                    if (!layerEdit[pos][1]) {
                        if (!guiConfirmBox("Clipboard includes extended collision, alpha, and/or animation data, which layer does not support- continue?", "Extended Data Mismatch")) return 0;
                        warnedFx = 1;
                    }
                }

                // Collision data
                if ((pHasFx) && (!warnedFx)) {
                    if (!layerEdit[pos][2]) {
                        if (!guiConfirmBox("Clipboard includes effects data, which layer does not support- continue?", "Effects Data Mismatch")) return 0;
                        warnedFx = 1;
                    }
                }
            
                ++matchedLayers;
                if (matchedLayers == pNumLayers) break;
            }
        }
        
        // Too many layers?
        if (matchedLayers < pNumLayers) {
            if (!guiConfirmBox("Clipboard contains more layers than marked for editing- continue?", "Layer Mismatch")) return 0;
        }
        
        world->undo.preUndoBlock();
        doneSelection();
        undoStoreSelect(0, 0, layerWidth, layerHeight, 1);
        world->undo.postUndoBlock();

        // "Default" data/fx if paste data doesn't supply it
        Uint32 defData;
        Uint32 defExt;
        Uint32 defFx;
        imagebar->getDataExt(defData, defExt, defFx);

        // Now actually paste layer data
        affectBit = 1;
        matchedLayers = 0;
        // @TODO: If only one layer's worth of data, always paste to cursor layer
        // this may require adjusting the data clearing portion, below
        for (pos = 0; pos < numLayers; ++pos, affectBit <<= 1) {
            if (layersAffect & affectBit) {
                memSet32(layerEdit[pos][0], Layer::LAYER_TILE_DEFAULT, layerWidth * layerHeight);
                if (layerEdit[pos][1]) memSet32(layerEdit[pos][1], Layer::LAYER_EXT_DEFAULT, layerWidth * layerHeight);
                if (layerEdit[pos][2]) memSet32(layerEdit[pos][2], Layer::LAYER_FX_DEFAULT, layerWidth * layerHeight);
                clipboardPasteLayer(matchedLayers, layerEdit[pos][0], layerEdit[pos][1], layerEdit[pos][2], 0, 0, layerWidth, layerHeight, layerWidth, defData, defExt, defFx);

                ++matchedLayers;
                if (matchedLayers == pNumLayers) {
                    // So we start at the right layer for clearing data
                    ++pos;
                    affectBit <<= 1;
                    break;
                }
            }
        }
        
        // (any remaining layers- clear data)
        for (; pos < numLayers; ++pos, affectBit <<= 1) {
            if (layersAffect & affectBit) {
                memSet32(layerEdit[pos][0], Layer::LAYER_TILE_DEFAULT, layerWidth * layerHeight);
                if (layerEdit[pos][1]) memSet32(layerEdit[pos][1], Layer::LAYER_EXT_DEFAULT, layerWidth * layerHeight);
                if (layerEdit[pos][2]) memSet32(layerEdit[pos][2], Layer::LAYER_FX_DEFAULT, layerWidth * layerHeight);
            }
        }
        
        // Determine selection mask (select all, then deselect empties)
        memset(selected, 255, selectedPitch * layerHeight);
        selectionRect.x = 0;
        selectionRect.y = 0;
        selectionRect.w = layerWidth;
        selectionRect.h = layerHeight;
        selectionMode = SELECTION_OPAQUE;
        fixSelectionRect(1);

        // Possible nothing was actually pasted
        fixSelectionRect();
        updateCopyTool();
        if (selectionRect.w) {
            // @TODO: Move selection to ensure it's visible; may affect dirty below
            // Should ideally appear near previous selection (but not on top) or at cursor, adjusted to be on screen
        }

        // Dirty
        setDirtyRect(selectionRect);
        
        return TOOLS_SELECT;
    }
    
    return 0;
}

void SceneEditLayer::copySelection() { start_func
    if (selectionRect.w) {
        int setFloat = 0;
        
        // If not floating, float it to copy; backup selection
        if (selectionMode == SELECTION_OUTLINE) {
            memcpy(backupSelected, selected, selectedPitch * layerHeight);
            backupSelectionRect = selectionRect;

            setFloat = 1;
            floatSelection(1); // Will NOT throw undo
        }

        // Ensure there's a selection still
        if (selectionRect.w) {
            // We copy what's in the floating selection
            const void* cTileset[MAX_LAYERS];
            Uint32* cData[MAX_LAYERS];
            Uint32* cDataExt[MAX_LAYERS];
            Uint32* cDataFx[MAX_LAYERS];
            int cNumLayers = 0;
            
            // Copy each active layer
            int affectBit = 1;
            for (int pos = 0; pos < numLayers; ++pos, affectBit <<= 1) {
                if (layersAffect & affectBit) {
                    cTileset[cNumLayers] = scene->getLayer(pos)->getTileSet();
                    cData[cNumLayers] = layerEdit[pos][0];
                    cDataExt[cNumLayers] = layerEdit[pos][1];
                    cDataFx[cNumLayers] = layerEdit[pos][2];
                    ++cNumLayers;
                }
            }
            
            clipboardCopy(cNumLayers, cTileset, cData, cDataExt, cDataFx, selectionRect.x, selectionRect.y, selectionRect.w, selectionRect.h, layerWidth);
        }
        
        // If was floating, defloat it now
        if (setFloat) {
            refreshData(0);
            selectionMode = SELECTION_OUTLINE;
            memcpy(selected, backupSelected, selectedPitch * layerHeight);
            selectionRect = backupSelectionRect;
        }
    }
}

void SceneEditLayer::deleteSelection() throw_Undo { start_func
    if ((selectionMode == SELECTION_OUTLINE) && (selectionRect.w)) {
        // Float to nowhere
        floatSelection(0, 1);
    }
    else if (selectionRect.w) {
        // Delete floating selection
        clearSelection();
    }
}

void SceneEditLayer::mergeSelection() throw_Undo { start_func
    // (no scene = closing)
    if ((selectionMode != SELECTION_OUTLINE) && (scene)) {
        world->undo.preUndoBlock();

        // Paste selection, with offset
        if (selectionRect.w) {
            undoStoreLayer(selectionRect.x + selectionXOffs, selectionRect.y + selectionYOffs, selectionRect.w, selectionRect.h, 0);
            undoStoreSelect(selectionRect.x, selectionRect.y, selectionRect.w, selectionRect.h);

            // Underlay existing layer data under selection on each layer
            Uint32* tempFloat = NULL;
            Uint32* tempFloatExt = NULL;
            Uint32* tempFloatFx = NULL;
            
            Rect affect = { 0, 0, layerWidth, layerHeight };
            int affectBit = 1;
            int layerSize = layerWidth * layerHeight;
            for (int pos = 0; pos < numLayers; ++pos, affectBit <<= 1) {
                if (layersAffect & affectBit) {
                    // Allocate temporary storage to overlay selection to
                    tempFloat = new Uint32[layerSize];
                    if (layerEdit[pos][1]) {
                        tempFloatExt = new Uint32[layerSize];
                    }
                    if (layerEdit[pos][2]) {
                        tempFloatFx = new Uint32[layerSize];
                    }
                    
                    // Grab original layer data
                    scene->getLayerEdit(pos)->loadLayer(tempFloat, tempFloatExt, tempFloatFx, layerWidth, affect);
    
                    // Scan selection and overlay selected data that isn't tile 0
                    Uint32* src = layerEdit[pos][0];
                    Uint32* srcExt = layerEdit[pos][1];
                    Uint32* srcFx = layerEdit[pos][2];
                    Uint32* dest = tempFloat + selectionXOffs + selectionYOffs * layerWidth;
                    Uint32* destExt = tempFloatExt;
                    Uint32* destFx = tempFloatFx;
                    if (destExt) destExt += selectionXOffs + selectionYOffs * layerWidth;
                    if (destFx) destFx += selectionXOffs + selectionYOffs * layerWidth;

                    for (int y = 0; y < layerHeight; ++y) {
                        for (int x = 0; x < layerWidth; ++x) {
                            if ((*src & Layer::LAYER_TILE_INDEX) != 0) {
                                if ((x < layerWidth - selectionXOffs) && (x >= -selectionXOffs) && (y < layerHeight - selectionYOffs) && (y >= -selectionYOffs)) {
                                    *dest = *src;
                                    if (srcExt) {
                                        *destExt = *srcExt;
                                    }
                                    if (srcFx) {
                                        *destFx = *srcFx;
                                    }
                                }
                            }
                            ++src;
                            ++dest;
                            if (srcExt) {
                                ++srcExt;
                                ++destExt;
                            }
                            if (srcFx) {
                                ++srcFx;
                                ++destFx;
                            }
                        }
                    }
                    
                    // Replace our layer data
                    delete[] layerEdit[pos][0];
                    delete[] layerEdit[pos][1];
                    delete[] layerEdit[pos][2];
                    layerEdit[pos][0] = tempFloat;
                    layerEdit[pos][1] = tempFloatExt;
                    layerEdit[pos][2] = tempFloatFx;
                    tempFloat = NULL;
                    tempFloatExt = NULL;
                    tempFloatFx = NULL;

                    // Apply data
                    scene->getLayerEdit(pos)->saveLayer(layerEdit[pos][0], layerEdit[pos][1], layerEdit[pos][2], layerWidth, affect, this);
                }
            }
        }
        
        // Clear selection (also sets dirty for us)
        clearSelection(0);
        world->undo.postUndoBlock();
    }
}

int SceneEditLayer::isInSelection(int x, int y) const { start_func
    if (!selectionRect.w) return 0;

    x -= selectionXOffs;
    y -= selectionYOffs;

    Uint8 bit = 1 << (x & 7);
    Uint8* selData = selected + (x >> 3) + y * selectedPitch;
    return *selData & bit;
}

void SceneEditLayer::fixSelectionRect(int deselEmpty) { start_func
    int minX = -1;
    int maxX;
    int minY;
    int maxY;

    Uint8 bit = 1;
    Uint8* source = selected;
    int dataOffset = 0;
    for (int y = 0; y < layerHeight; ++y) {
        for (int x = 0; x < layerWidth; ++x) {
            if (deselEmpty) {
                // Check all affected layers
                int empty = 1;
                for (int pos = 0; pos < numLayers; ++pos) {
                    if (layerEdit[pos][0]) {
                        if ((layerEdit[pos][0][dataOffset] & Layer::LAYER_TILE_INDEX) != 0) {
                            empty = 0;
                            break;
                        }
                    }
                }
                // Empty- deselct this bit
                if (empty) {
                    *source &= ~bit;
                }
            }
            if (*source & bit) {
                if (minX == -1) {
                    minX = maxX = x;
                    minY = maxY = y;
                }
                else {
                    if (x < minX) minX = x;
                    else if (x > maxX) maxX = x;
                    if (y < minY) minY = y;
                    else if (y > maxY) maxY = y;
                }
            }
            ++dataOffset;
            bit <<= 1;
            if (bit == 0) {
                bit = 1;
                ++source;
            }
        }
        // If bit just wrapped to 1, we have an even multiple
        // otherwise, move forward the final byte
        if (bit > 1) ++source;
        bit = 1;
    }
    
    if (minX == -1) {
        selectionRect.w = 0;
    }
    else {
        selectionRect = createRect(minX, minY, maxX, maxY);
    }
}

void SceneEditLayer::updateSpriteShown() { start_func
    int newSpriteShown = 0;
    int newSpriteX = spriteShownX;
    int newSpriteY = spriteShownY;

    // If we don't have input focus, don't show
    // If mouse isn't over us, and not showing at cursor, don't show
    if ((!partialFocus) || ((!hover) && (!spriteAtCursor))) {
        // (already set above)
    }

    // Show sprite if currently in use tool is a sprite tool OR
    // current tool is a sprite tool and not in use
    else if ((toolActive == TOOLS_PLACEITEM) || (toolActive == TOOLS_PLACESERIES) ||
        ((!toolActive) &&
        ((toolL == TOOLS_PLACEITEM) || (toolL == TOOLS_PLACESERIES) ||
         (toolR == TOOLS_PLACEITEM) || (toolR == TOOLS_PLACESERIES)))) {
        newSpriteShown = 1;
        
        // Now determine position
        if (spriteAtCursor) {
            newSpriteX = cursorX * tileWidth;
            newSpriteY = cursorY * tileHeight;
        }
        else {
            newSpriteX = pixelMouseX;
            newSpriteY = pixelMouseY;
        }
    }
    
    // Change from last time?
    if ((newSpriteShown != spriteShown) || (newSpriteX != spriteShownX) || (newSpriteY != spriteShownY)) {
        // @TODO: Dirty old and new areas only
        setDirty(1);
        
        spriteShown = newSpriteShown;
        spriteShownX = newSpriteX;
        spriteShownY = newSpriteY;
    }
}

void SceneEditLayer::mousePointer(int mouseX, int mouseY) { start_func
    lastMouseX = mouseX;
    lastMouseY = mouseY;
    mousePointer();
}

void SceneEditLayer::mousePointer() { start_func
    if (!hover) return;

    if ((toolActive == TOOLS_SELECT) || (toolActive == TOOLS_SELECTELLIPSE) ||
        (toolActive == TOOLS_WAND) || (toolActive == TOOLS_SELECTITEM)) {
        if (toolAlt) {
            selectMouse(MOUSE_SUBTRACT);
            return;
        }
        else if (toolCtrl) {
            selectMouse(MOUSE_ADD);
            return;
        }
    }
    if ((toolActive == TOOLS_SELECTDRAG) || (toolActive == TOOLS_SELECTITEMDRAG)) {
        selectMouse(MOUSE_FOURDIRECTION);
        return;
    }
    if (((!toolActive) && ((toolL == TOOLS_SELECT) || (toolL == TOOLS_SELECTELLIPSE) ||
                           (toolL == TOOLS_WAND)))
        || (toolL == TOOLS_SELECTITEM)) {
        if (SDL_GetModState() & KMOD_ALT) {
            selectMouse(MOUSE_SUBTRACT);
            return;
        }
        if (SDL_GetModState() & KMOD_CTRL) {
            selectMouse(MOUSE_ADD);
            return;
        }
        
        if (toolL == TOOLS_SELECTITEM) {
            if (isInSpawnSelection(lastMouseX, lastMouseY)) {
                selectMouse(MOUSE_FOURDIRECTION);
                return;
            }
        }
        else {
            if (isInSelection(lastMouseX / tileWidth, lastMouseY / tileHeight)) {
                selectMouse(MOUSE_FOURDIRECTION);
                return;
            }
        }
    }    
        
    selectMouse(MOUSE_NORMAL);
}

Window::CommandSupport SceneEditLayer::supportsCommand(int code) const { start_func
    switch (code) {
        // Options
        case VIEW_GRID:
            return (Window::CommandSupport)((enableGrid ? Window::COMMAND_SELECTED : 0) | Window::COMMAND_CHECKBOX | Window::COMMAND_ENABLE);
            
        case TOOLS_CONTIGUOUS:
            if ((toolL == TOOLS_WAND) || (toolL == TOOLS_FILL) || (toolR == TOOLS_WAND) || (toolR == TOOLS_FILL)) {
                return (Window::CommandSupport)((contiguous ? Window::COMMAND_SELECTED : 0) | Window::COMMAND_CHECKBOX | Window::COMMAND_ENABLE);
            }
            else return (Window::CommandSupport)((contiguous ? Window::COMMAND_SELECTED : 0) | Window::COMMAND_CHECKBOX | Window::COMMAND_DISABLE);;

        case TOOLS_PEN:
        case TOOLS_LINE:
        case TOOLS_RECT:
        case TOOLS_RECTFILL:
        case TOOLS_ELLIPSE:
        case TOOLS_ELLIPSEFILL:
        case TOOLS_SELECT:
        case TOOLS_SELECTELLIPSE:
        case TOOLS_DROPPER:
        case TOOLS_FILL:
        case TOOLS_WAND:
            return (Window::CommandSupport)(((toolL == code) ? Window::COMMAND_SELECTED : 0) | Window::COMMAND_RADIO | Window::COMMAND_ENABLE);

        // These are always available (techincally you could have 1 or 0
        //   colors/images available, but not worth worrying about)
        case TOOLS_CHOOSE:
        case TOOLS_CHOOSER:
        case EDIT_SELECTALL:
        case TOOLS_SETTINGS:
        case TOOLS_EDITCOLOR:
        case TOOLS_NEXTCOLOR:
        case TOOLS_PREVCOLOR:
        case TOOLS_NEXTIMAGE:
        case TOOLS_PREVIMAGE:
        case NEW_LAYER:
            return Window::COMMAND_ENABLE;
            
        case VIEW_PREV:
            for (int pos = 0; pos < cursorLayer; ++pos) {
                if (layersAvailable & (1 << pos)) return Window::COMMAND_ENABLE;
            }
            return Window::COMMAND_DISABLE;
        
        case VIEW_NEXT:
            for (int pos = cursorLayer + 1; pos < numLayers; ++pos) {
                if (layersAvailable & (1 << pos)) return Window::COMMAND_ENABLE;
            }
            return Window::COMMAND_DISABLE;

        case VIEW_ALLLAYER:
        case VIEW_DIMLAYER:
        case VIEW_NOLAYER:
            if (numLayers > 1) return Window::COMMAND_ENABLE;
            return Window::COMMAND_DISABLE;

        case EDIT_COPY:
        case EDIT_CUT:
        case EDIT_DELETE:
        case EDIT_DESELECTALL:
            if (selectionRect.w) return Window::COMMAND_ENABLE;
            else return Window::COMMAND_DISABLE;
        
        case EDIT_PASTE:
            if (canConvertClipboard(CLIPBOARD_LAYER)) return Window::COMMAND_ENABLE;
            else return Window::COMMAND_DISABLE;
    }
    
    return world->supportsCommand(code);
}

void SceneEditLayer::newToolSelected() { start_func
    // @TODO: abort currently used tool?
    if ((toolL == TOOLS_PLACEITEM) || (toolL == TOOLS_PLACESERIES) || (toolL == TOOLS_SELECTITEM)) spriteMode = 1;
    else spriteMode = 0;
    updateSpriteShown();
}

void SceneEditLayer::startToolIfMove() { start_func
    startToolOnMove = 1;
}

void SceneEditLayer::refresh() { start_func
    setDirty(1);
}

void SceneEditLayer::refreshLayers(Uint32 newLayersAffect, Uint32 newLayersView, Uint32 newLayersDim) { start_func
    // Determine new desired cursor layer, affected layers, visible/dim layers
    int newCursorLayer = layerlist->selectedLayer;
    if (newLayersAffect) {
        // Layers provided- just ensure cursor layer valid and then apply to layer list
        int cursorBit = 1 << newCursorLayer;
        if (!(newLayersAffect & cursorBit)) {
            for (newCursorLayer = 0, cursorBit = 1; newCursorLayer < numLayers; ++newCursorLayer, cursorBit <<= 1) {
                if (newLayersAffect & cursorBit) break;
            }
            assert(newCursorLayer < numLayers);
        }
        layerlist->layerList->setBitMask(newLayersAffect, newLayersView, newLayersDim, newCursorLayer);
    }
    else {
        // Grab layers from layer list
        layerlist->layerList->determineBitMasks(newLayersView, newLayersDim, newLayersAffect);
    }
    
    // Compare to current settings and determine what needs redrawing or reloading
    // refresh = need to reload layer data
    int refresh = 0;

    // Merge any selection before changing affect bits unless from an undo
    if ((newLayersAffect != layersAffect) || (newCursorLayer != cursorLayer)) {
        // (we merge event if just "affected layers" changes because that changes what
        // the selection would be applying to)
        try {
            mergeSelection();
        }
        catch (UndoException& e) {
            // If undo fails, we force layerlist to previous set of layers
            layerlist->layerList->setBitMask(layersAffect, layersView, layersDim, cursorLayer);
            return;
        }
    }

    if (newLayersAffect != layersAffect) {
        layersAffect = newLayersAffect;
        refresh = 1;
    }
    if (newCursorLayer != cursorLayer) {
        cursorLayer = newCursorLayer;
        refresh = 1;
    }
    if (refresh) {
        reloadLayerStats();
        refreshData(0);
    }
    
    // Now, refresh = need to redraw
    if ((newLayersView != layersView) || (newLayersDim != layersDim)) {
        layersView = newLayersView;
        layersDim = newLayersDim;
        refresh = 1;
    }
    
    if (refresh) {
        recheckSpawnSelection();
        setDirty(1);
    }
}

void SceneEditLayer::selectEllipse(int x, int y, int rx, int ry, int set) { start_func
    // ** Code modified from sge_primitives.cpp **
	int ix, iy;
	int h, i, j, k;
	int oh, oi, oj, ok;

	if (rx < 1) rx = 1;
	if (ry < 1) ry = 1;

	oh = oi = oj = ok = 0xFFFF;

	if (rx > ry) {
  		ix = 0;
   		iy = rx * 64;

 		do {
            h = (ix + 32) >> 6;
	 		i = (iy + 32) >> 6;
	 		j = (h * ry) / rx;
	 		k = (i * ry) / rx;

	 		if ((k != ok) && (k != oj)) {
                if (k) {
                    selectRect(x - h, y - k, x + h, y - k, set);
                    selectRect(x - h, y + k, x + h, y + k, set);
				}
                else selectRect(x - h, y, x + h, y, set);
				ok = k;
			}

	 		if ((j != oj) && (j != ok) && (k != j)) {
                if (j) {
                    selectRect(x - i, y - j, x + i, y - j, set);
                    selectRect(x - i, y + j, x + i, y + j, set);
				}
                else selectRect(x - i, y, x + i, y, set);
				oj = j;
	 		}

			ix = ix + iy / rx;
	 		iy = iy - ix / rx;
		} while (i > h);
	}
	else {
		ix = 0;
		iy = ry * 64;

		do {
	 		h = (ix + 32) >> 6;
	 		i = (iy + 32) >> 6;
	 		j = (h * rx) / ry;
	 		k = (i * rx) / ry;

	 		if ((i != oi) && (i != oh)) {
                if (i) {
                    selectRect(x - j, y - i, x + j, y - i, set);
                    selectRect(x - j, y + i, x + j, y + i, set);
				}
                else selectRect(x - j, y, x + j, y, set);
				oi = i;
	 		}

	 		if ((h != oh) && (h != oi) && (i != h)) {
                if (h) {
                    selectRect(x - k, y - h, x + k, y - h, set);
                    selectRect(x - k, y + h, x + k, y + h, set);
				}
                else selectRect(x - k, y, x + k, y, set);
				oh = h;
	 		}

	 		ix = ix + iy / ry;
	 		iy = iy - ix / ry;
  		} while(i > h);
	}
}

void SceneEditLayer::selectRect(int x1, int y1, int x2, int y2, int set) { start_func
    Rect rect = createRect(x1, y1, x2, y2);
    Rect bound = { 0, 0, layerWidth, layerHeight };
    if (intersectRects(rect, bound)) {
        y1 = rect.y;
        y2 = y1 + rect.h - 1;
        x1 = rect.x;
        x2 = x1 + rect.w - 1;
    
        for (; y1 <= y2; ++y1) {
            // Place in bitarray terms
            Uint8 bit = 1 << (x1 & 7);
            Uint8* selData = selected + (x1 >> 3) + y1 * selectedPitch;
            
            for (int x = x1; x <= x2; ++x) {
                if (set) {
                    *selData |= bit;
                }
                else {
                    *selData &= ~bit;
                }
                bit <<= 1;
                if (bit == 0) {
                    bit = 1;
                    ++selData;
                }
            }
        }
    }
}

// @TODO: account for selection masking
void SceneEditLayer::layerDrawEllipse(int x, int y, int rx, int ry, Uint32* data, Uint32 mask, Uint32 set) const { start_func
    // (skip if nothing being drawn)
    if ((mask == 0xFFFFFFFF) && (set == 0)) return;

    // ** Code modified from sge_primitives.cpp **
	int ix, iy;
	int h, i, j, k;
	int oh, oi, oj, ok;
	Uint32* target;

	if (rx < 1) rx = 1;
	if (ry < 1) ry = 1;

	h = i = j = k = 0xFFFF;
	
#define PUTPIXEL(x, y) {\
    if (((x) >= 0) && ((x) < layerWidth) && ((y) >= 0) && ((y) < layerHeight)) {\
        target = data + (x) + (y) * layerWidth;\
        *target = (*target & mask) | set;\
    }\
}

	if (rx > ry) {
		ix = 0;
		iy = rx * 64;

 		do {
			oh = h;
	 		oi = i;
	 		oj = j;
			ok = k;

	 		h = (ix + 32) >> 6;
	 		i = (iy + 32) >> 6;
	 		j = (h * ry) / rx;
	 		k = (i * ry) / rx;

	 		if (((h != oh) || (k != ok)) && (h < oi)) {
	 		    PUTPIXEL(x + h, y + k);
	   			if (h) PUTPIXEL(x - h, y + k);
	   			if (k) {
			        PUTPIXEL(x + h, y - k);
	     			if (h) PUTPIXEL(x - h, y - k);
	  			}
			}

	 		if (((i != oi) || (j != oj)) && (h < i)) {
	 		    PUTPIXEL(x + i, y + j);
	  			if (i) PUTPIXEL(x - i, y + j);
	  			if (j) {
	  			    PUTPIXEL(x + i, y - j);
	   				if (i) PUTPIXEL(x - i, y - j);
	 			}
	 		}

			ix = ix + iy / rx;
	 		iy = iy - ix / rx;
		} while (i > h);
	}
	else {
		ix = 0;
		iy = ry * 64;

  		do {
	 		oh = h;
	 		oi = i;
	 		oj = j;
	 		ok = k;

	 		h = (ix + 32) >> 6;
	 		i = (iy + 32) >> 6;
	 		j = (h * rx) / ry;
	 		k = (i * rx) / ry;

	 		if (((j != oj) || (i != oi)) && (h < i)) {
	 		    PUTPIXEL(x + j, y + i);
	   			if (j) PUTPIXEL(x - j, y + i);
	   			if (i) {
	   			    PUTPIXEL(x + j, y - i);
	     			if (j) PUTPIXEL(x - j, y - i);
	  			}
	 		}

	 		if (((k != ok) || (h != oh)) && (h < oi)) {
	 		    PUTPIXEL(x + k, y + h);
	  			if (k) PUTPIXEL(x - k, y + h);
	  			if (h) {
	  			    PUTPIXEL(x + k, y - h);
	   				if (k) PUTPIXEL(x - k, y - h);
	 			}
	 		}

	 		ix = ix + iy / ry;
	 		iy = iy - ix / ry;
  		} while(i > h);
	}	
}

// @TODO: account for selection masking
void SceneEditLayer::layerDrawEllipseFill(int x, int y, int rx, int ry, Uint32* data, Uint32 mask, Uint32 set) const { start_func
    // (skip if nothing being drawn)
    if ((mask == 0xFFFFFFFF) && (set == 0)) return;

    // ** Code modified from sge_primitives.cpp **
	int ix, iy;
	int h, i, j, k;
	int oh, oi, oj, ok;

	if (rx < 1) rx = 1;
	if (ry < 1) ry = 1;

	oh = oi = oj = ok = 0xFFFF;

	if (rx > ry) {
  		ix = 0;
   		iy = rx * 64;

 		do {
            h = (ix + 32) >> 6;
	 		i = (iy + 32) >> 6;
	 		j = (h * ry) / rx;
	 		k = (i * ry) / rx;

	 		if ((k != ok) && (k != oj)) {
                if (k) {
                    layerDrawRect(x - h, y - k, x + h, y - k, data, mask, set);
                    layerDrawRect(x - h, y + k, x + h, y + k, data, mask, set);
				}
                else layerDrawRect(x - h, y, x + h, y, data, mask, set);
				ok = k;
			}

	 		if ((j != oj) && (j != ok) && (k != j)) {
                if (j) {
                    layerDrawRect(x - i, y - j, x + i, y - j, data, mask, set);
                    layerDrawRect(x - i, y + j, x + i, y + j, data, mask, set);
				}
                else layerDrawRect(x - i, y, x + i, y, data, mask, set);
				oj = j;
	 		}

			ix = ix + iy / rx;
	 		iy = iy - ix / rx;
		} while (i > h);
	}
	else {
		ix = 0;
		iy = ry * 64;

		do {
	 		h = (ix + 32) >> 6;
	 		i = (iy + 32) >> 6;
	 		j = (h * rx) / ry;
	 		k = (i * rx) / ry;

	 		if ((i != oi) && (i != oh)) {
                if (i) {
                    layerDrawRect(x - j, y - i, x + j, y - i, data, mask, set);
                    layerDrawRect(x - j, y + i, x + j, y + i, data, mask, set);
				}
                else layerDrawRect(x - j, y, x + j, y, data, mask, set);
				oi = i;
	 		}

	 		if ((h != oh) && (h != oi) && (i != h)) {
                if (h) {
                    layerDrawRect(x - k, y - h, x + k, y - h, data, mask, set);
                    layerDrawRect(x - k, y + h, x + k, y + h, data, mask, set);
				}
                else layerDrawRect(x - k, y, x + k, y, data, mask, set);
				oh = h;
	 		}

	 		ix = ix + iy / ry;
	 		iy = iy - ix / ry;
  		} while(i > h);
	}
}

// @TODO: account for selection masking
// @TODO: additional matchmask to cover ext data as well
Rect SceneEditLayer::layerFloodFill(int x, int y, Uint32 matchMask, Uint32* data1, Uint32 mask1, Uint32 set1, Uint32* data2, Uint32 mask2, Uint32 set2) const { start_func
    assert(data1);

    // Min/Max area
    Rect result = { 0, 0, 0, 0 };

    // Clip area
    if ((x < 0) || (y < 0) || (x >= layerWidth) || (y >= layerHeight)) return result;
    
    // Extreme points
    int x1 = x;
    int x2 = x;
    int y1 = y;
    int y2 = y;
    
    // Stack
#define FLOOD_STACK_SIZE 250
    struct {
        Uint32* d1;
        Uint32* d2;
        int x;
        int y;
        int yD;
    } floodStack[FLOOD_STACK_SIZE];
    int stackPos = 0;
    
#define PUSH(dp1, dp2, xp, yp, ydiff) {\
    if (stackPos < FLOOD_STACK_SIZE) {\
        floodStack[stackPos].d1 = dp1;\
        floodStack[stackPos].d2 = dp2;\
        floodStack[stackPos].x = xp;\
        floodStack[stackPos].y = yp;\
        floodStack[stackPos++].yD = ydiff;\
    }\
}

#define POP(dp1, dp2, xp, yp, ydiff) {\
    assert(stackPos);\
    dp1 = floodStack[--stackPos].d1;\
    dp2 = floodStack[stackPos].d2;\
    xp = floodStack[stackPos].x;\
    yp = floodStack[stackPos].y;\
    ydiff = floodStack[stackPos].yD;\
}

    // Starting point etc.
    Uint32* dataPoint1 = data1 + x + y * layerWidth;
    Uint32* dataPoint2 = NULL;
    if (data2) dataPoint2 = data2 + x + y * layerWidth;

    // Determine what we're matching against, don't fill if matches what we're doing
    Uint32 target = *dataPoint1 & matchMask;
    if (((target & mask1) | set1) == target) return result;

    // Work areas
    int top, bottom;
    int lX;
    int yDiff;
    
    // Push starting point
    PUSH(dataPoint1, dataPoint2, x, y, 0);
    
    while (stackPos) {
        // Next point to fill, then scan left and right
        POP(dataPoint1, dataPoint2, x, y, yDiff);
        dataPoint1 += yDiff * layerWidth;
        if (dataPoint2) dataPoint2 += yDiff * layerWidth;
        y += yDiff;
        
        // Don't fill given point directly- instead, we'll scan it manually in the second loop
        // Update extreme points- y
        if (y < y1) y1 = y;
        if (y > y2) y2 = y;
        
        // Check both top and bottom for matches
        top = bottom = 1;
        
        // Fill as far left as possible
        lX = x;
        while ((lX > 0) && ((dataPoint1[-1] & matchMask) == target)) {
            --lX;
            --dataPoint1;
            *dataPoint1 = (*dataPoint1 & mask1) | set1;
            if (dataPoint2) {
                --dataPoint2;
                *dataPoint2 = (*dataPoint2 & mask2) | set2;
            }
            
            // Is point above fillable?
            if ((y > 0) && ((dataPoint1[-layerWidth] & matchMask) == target)) {
                // Are we pushing "above" points right now?
                if (top) {
                    // Push onto stack
                    PUSH(dataPoint1, dataPoint2, lX, y, -1);
                    top = 0;
                }
            }
            // Not fillable, we will now push the next "above" point we find
            else top = 1;
            
            // Is point below fillable?
            if ((y < layerHeight - 1) && ((dataPoint1[layerWidth] & matchMask) == target)) {
                // Are we pushing "below" points right now?
                if (bottom) {
                    // Push onto stack
                    PUSH(dataPoint1, dataPoint2, lX, y, 1);
                    bottom = 0;
                }
            }
            // Not fillable, we will now push the next "below" point we find
            else bottom = 1;
        }
        
        // Update extreme left point
        if (lX < x1) x1 = lX;
        
        // Recenter, minus one, so that we scan the center/given point as well
        --x;
        dataPoint1 += x - lX;
        if (dataPoint2) dataPoint2 += x - lX;
        top = bottom = 1;
        
        // Fill as far right as possible; this loop intentionally catches our first, central point
        while ((x < layerWidth - 1) && ((dataPoint1[1] & matchMask) == target)) {
            ++x;
            ++dataPoint1;
            *dataPoint1 = (*dataPoint1 & mask1) | set1;
            if (dataPoint2) {
                ++dataPoint2;
                *dataPoint2 = (*dataPoint2 & mask2) | set2;
            }
            
            // Is point above fillable?
            if ((y > 0) && ((dataPoint1[-layerWidth] & matchMask) == target)) {
                // Are we pushing "above" points right now?
                if (top) {
                    // Push onto stack
                    PUSH(dataPoint1, dataPoint2, x, y, -1);
                    top = 0;
                }
            }
            // Not fillable, we will now push the next "above" point we find
            else top = 1;
            
            // Is point below fillable?
            if ((y < layerHeight - 1) && ((dataPoint1[layerWidth] & matchMask) == target)) {
                // Are we pushing "below" points right now?
                if (bottom) {
                    // Push onto stack
                    PUSH(dataPoint1, dataPoint2, x, y, 1);
                    bottom = 0;
                }
            }
            // Not fillable, we will now push the next "below" point we find
            else bottom = 1;
        }

        // Update extreme right point
        if (x > x2) x2 = x;
    }
    
    result.x = x1;
    result.y = y1;
    result.w = x2 - x1 + 1;
    result.h = y2 - y1 + 1;
    return result;
}

// @TODO: account for selection masking
Rect SceneEditLayer::layerMatchFill(int x, int y, Uint32 matchMask, Uint32* data1, Uint32 mask1, Uint32 set1, Uint32* data2, Uint32 mask2, Uint32 set2) const { start_func
    assert(data1);

    // Min/Max area
    Rect result = { 0, 0, 0, 0 };

    // Clip area
    if ((x < 0) || (y < 0) || (x >= layerWidth) || (y >= layerHeight)) return result;
    
    // Extreme points
    int x1 = x;
    int x2 = x;
    int y1 = y;
    int y2 = y;
    
    // Determine what we're matching against, don't fill if matches what we're doing
    Uint32 target = data1[x + y * layerWidth] & matchMask;
    if (((target & mask1) | set1) == target) return result;

    for (y = 0; y < layerHeight; ++y) {
        for (x = 0; x < layerWidth; ++x) {
            if ((*data1 & matchMask) == target) {
                *data1 = (*data1 & mask1) | set1;
                if (data2) *data2 = (*data2 & mask2) | set2;
                
                // Update extreme points
                if (y < y1) y1 = y;
                if (y > y2) y2 = y;
                if (x < x1) x1 = x;
                if (x > x2) x2 = x;
            }
            
            ++data1;
            if (data2) ++data2;
        }
    }
    
    result.x = x1;
    result.y = y1;
    result.w = x2 - x1 + 1;
    result.h = y2 - y1 + 1;
    return result;
}

// @TODO: account for selection masking
void SceneEditLayer::layerDrawRect(int x1, int y1, int x2, int y2, Uint32* data, Uint32 mask, Uint32 set) const { start_func
    assert(data);

    // (skip if nothing being drawn)
    if ((mask == 0xFFFFFFFF) && (set == 0)) return;

    Rect affect = createRect(x1, y1, x2, y2);
    Rect bound = { 0, 0, layerWidth, layerHeight };
    if (!intersectRects(affect, bound)) return;
    
    data += affect.y * layerWidth + affect.x;
    for (int row = affect.h; row > 0; --row) {
        for (int col = affect.w; col > 0; --col) {
            *data = (*data & mask) | set;
            ++data;
        }
        data += layerWidth - affect.w;
    }
}

void SceneEditLayer::layerDrawBox(int x1, int y1, int x2, int y2, Uint32* data, Uint32 mask, Uint32 set) const { start_func
    layerDrawRect(x1, y1, x2, y1, data, mask, set);
    layerDrawRect(x1, y1, x1, y2, data, mask, set);
    layerDrawRect(x2, y1, x2, y2, data, mask, set);
    layerDrawRect(x1, y2, x2, y2, data, mask, set);
}

// @TODO: account for selection masking
void SceneEditLayer::layerDrawLine(int x1, int y1, int x2, int y2, Uint32* data, Uint32 mask, Uint32 set) const { start_func
    assert(data);
    
    // (skip if nothing being drawn)
    if ((mask == 0xFFFFFFFF) && (set == 0)) return;

    Sint16 x = x1;
    Sint16 y = y1;
    Sint16 dy = y2 - y1;
    Sint16 dx = x2 - x1;
  
    Sint16 G, DeltaG1, DeltaG2;//, minG, maxG;
    Sint16 inc = 1;

    if (abs(dy) < abs(dx)) {
        if (dx < 0) {
            dx = -dx;
            dy = -dy;
    
            y1 = y2;
            y2 = y;
            y = y1;
  
            x1 = x2;
            x2 = x;
            x = x1;
        }
        if (dy < 0) {
            dy = -dy;
            inc = -1;
        }
  
        G = 2 * dy - dx;
        DeltaG1 = 2 * (dy - dx);
        DeltaG2 = 2 * dy;
  
        data += y * layerWidth + x;
        if ((x >= 0) && (y >= 0) && (x < layerWidth) && (y < layerHeight)) *data = (*data & mask) | set;

        while (++x <= x2) {
            ++data;
            if (G > 0) { G += DeltaG1; y += inc; data += inc * layerWidth; }
            else G += DeltaG2;
  
            if ((x >= 0) && (y >= 0) && (x < layerWidth) && (y < layerHeight)) *data = (*data & mask) | set;
        }  
    }
    else {
        if (dy < 0) {
            dx = -dx;
            dy = -dy;
    
            y1 = y2;
            y2 = y;
            y = y1;
  
            x1 = x2;
            x2 = x;
            x = x1;
        }
        if (dx < 0) {
            dx = -dx;
            inc = -1;
        }
  
        G = 2 * dx - dy;
        //minG = maxG = G;
        DeltaG1 = 2 * (dx - dy);
        DeltaG2 = 2 * dx;
  
        data += y * layerWidth + x;
        if ((x >= 0) && (y >= 0) && (x < layerWidth) && (y < layerHeight)) *data = (*data & mask) | set;

        while (++y <= y2) {
            data += layerWidth;
            if (G > 0) { G += DeltaG1; x += inc; data += inc; }
            else G += DeltaG2;
  
            if ((x >= 0) && (y >= 0) && (x < layerWidth) && (y < layerHeight)) *data = (*data & mask) | set;
        }  
    }
}

void SceneEditLayer::swapSelectionParameters(int& xoffs, int& yoffs, int& mode) { start_func
    swap(xoffs, selectionXOffs);
    swap(yoffs, selectionYOffs);
    if (mode != selectionMode) {
        if (mode == SELECTION_OUTLINE) {
            refreshData(0);
        }
        else {
            Uint32 affectBit = 1;
            for (int pos = 0; pos < numLayers; ++pos, affectBit <<= 1) {
                if (layersAffect & affectBit) {
                    memSet32(layerEdit[pos][0], Layer::LAYER_TILE_DEFAULT, layerWidth * layerHeight);
                    if (layerEdit[pos][1]) memSet32(layerEdit[pos][1], Layer::LAYER_EXT_DEFAULT, layerWidth * layerHeight);
                    if (layerEdit[pos][2]) memSet32(layerEdit[pos][2], Layer::LAYER_FX_DEFAULT, layerWidth * layerHeight);
                }
            }
        }
    }
    swap(mode, selectionMode);

    // Refresh
    fixSelectionRect();
    setDirty(1);
}

Window::WindowType SceneEditLayer::windowType() const { start_func
    return WINDOW_CLIENT;
}

void SceneEditLayer::setDirtySelectedSpawns() { start_func
    setDirtyPixelBox(selectedSpawnRect.x, selectedSpawnRect.y,
                     selectedSpawnRect.x + selectedSpawnRect.w - 1,
                     selectedSpawnRect.y + selectedSpawnRect.h - 1);
}

void SceneEditLayer::copySpawnSelection() { start_func
    set<int>::iterator pos = selectedSpawns.begin();
    set<int>::iterator end = selectedSpawns.end();
    vector<const SpawnEdit*> toCopy;
    vector<int> toCopyLayers;
        
    while (pos != end) {
        // Find spawn
        SpawnEdit* spawn = world->findSpawn(*pos);
        if (spawn) {
            // Determine layer
            int lPos = scene->findLayer(spawn->getLayer());
            if (lPos >= 0) {
                int bit = 1 << lPos;
                if (layersAffect & bit) {
                    toCopy.push_back(spawn);
                    toCopyLayers.push_back(lPos);
                }
            }
        }
        ++pos;
    }
    
    clipboardCopy(toCopy, toCopyLayers);
}

void SceneEditLayer::deleteSpawnSelection() throw_Undo { start_func
    set<int>::iterator pos = selectedSpawns.begin();
    set<int>::iterator end = selectedSpawns.end();

    world->undo.preUndoBlock();
    undoStoreSpawnSelect();
    while (pos != end) {
        // Find spawn
        SpawnEdit* spawn = world->findSpawn(*pos);
        if (spawn) {
            // Determine layer
            int lPos = scene->findLayer(spawn->getLayer());
            if (lPos >= 0) {
                int bit = 1 << lPos;
                if (layersAffect & bit) {
                    spawn->getLayer()->deleteSpawn(spawn, myFrame, this); // Exception point (undoes other deletions for us)
                }
            }
        }
        ++pos;
    }
    setDirtySelectedSpawns();
    selectedSpawns.clear();
    selectedSpawnRect.w = 0;
    world->undo.postUndoBlock();
}

void SceneEditLayer::clearSpawnSelection(int storeUndo) throw_Undo { start_func
    if (storeUndo) undoStoreSpawnSelect();
    if (!selectedSpawns.empty()) {
        setDirtySelectedSpawns();
        selectedSpawns.clear();
        selectedSpawnRect.w = 0;
    }
}

void SceneEditLayer::updatedSpawnSelection() { start_func
    recheckSpawnSelection();
    setDirty(1);
}

void SceneEditLayer::recheckSpawnSelection() { start_func
    set<int>::iterator pos = selectedSpawns.begin();
    set<int>::iterator end = selectedSpawns.end();
    set<int>::iterator check;
    selectedSpawnRect.w = 0;
    while (pos != end) {
        check = pos;
        ++pos;
        
        // This spawn still in one of our affected layers?
        int kill = 1;
        SpawnEdit* spawn = world->findSpawn(*check);
        if (spawn) {
            // Determine layer
            int lPos = scene->findLayer(spawn->getLayer());
            if (lPos >= 0) {
                int bit = 1 << lPos;
                if (layersAffect & bit) {
                    Rect spawnRect = { spawn->getX(), spawn->getY(), spawn->getW(), spawn->getH() };
                    boundRects(selectedSpawnRect, spawnRect);
                    kill = 0;
                }
            }
        }

        if (kill) selectedSpawns.erase(check);
    }
}

void SceneEditLayer::undoStoreSpawnSelect() throw_Undo { start_func
    world->undo.storeUndoLayerSpawnSelection(&selectedSpawns, myFrame);
}

int SceneEditLayer::isInSpawnSelection(int x, int y) { start_func
    // Find spawns in one of our affected layers
    set<int>::iterator pos = selectedSpawns.begin();
    set<int>::iterator end = selectedSpawns.end();
    while (pos != end) {
        // Find spawn
        SpawnEdit* spawn = world->findSpawn(*pos);
        if (spawn) {
            // Determine layer
            int lPos = scene->findLayer(spawn->getLayer());
            if (lPos >= 0) {
                int bit = 1 << lPos;
                if (layersAffect & bit) {
                    if ((x >= spawn->getX()) &&
                        (y >= spawn->getY()) &&
                        (x < spawn->getX() + spawn->getW()) &&
                        (y < spawn->getY() + spawn->getH()))
                            return spawn->getId();
                }
            }
        }
        ++pos;
    }
    return 0;
}

void SceneEditLayer::startToolSpawnSelectionDrag() { start_func
    startToolOnMove = 0;
    if (toolActive) return;

    toolActive = TOOLS_SELECTITEMDRAG;
    toolStartX = cursorSpriteX;
    toolStartY = cursorSpriteY;
    toolCtrl = SDL_GetModState() & KMOD_CTRL;
    toolAlt = SDL_GetModState() & KMOD_ALT;
    mousePointer();
    dragTool(1);
}

void SceneEditLayer::selectSpawnRect(int x1, int y1, int x2, int y2, int select, int selectAll) { start_func
    Rect area = createRect(x1, y1, x2, y2);
    
    int bit = 1;
    for (int lPos = 0; lPos < numLayers; ++lPos, bit <<= 1) {
        if (layersAffect & bit) {
            // Check all spawns in this layer
            LayerEdit* layer = scene->getLayerEdit(lPos);
            for (int sPos = layer->getSpawnCount() - 1; sPos >= 0; --sPos) {
                SpawnEdit* spawn = layer->getSpawn(sPos);
                Rect spawnRect = { spawn->getX(), spawn->getY(), spawn->getW(), spawn->getH() };
                if ((selectAll) || (intersectRects(spawnRect, area))) {
                    if (select) selectedSpawns.insert(spawn->getId());
                    else selectedSpawns.erase(spawn->getId());
                }
            }
        }
    }
}

void SceneEditLayer::moveSpawnSelection(int byX, int byY, int skipUndo) { start_func
    set<int>::iterator pos = selectedSpawns.begin();
    set<int>::iterator end = selectedSpawns.end();
    
    while (pos != end) {
        // Find spawn
        SpawnEdit* spawn = world->findSpawn(*pos);
        if (spawn) {
            // Determine layer
            int lPos = scene->findLayer(spawn->getLayer());
            if (lPos >= 0) {
                int bit = 1 << lPos;
                if (layersAffect & bit) {
                    spawn->setPos(spawn->getX() + byX, spawn->getY() + byY, skipUndo, myFrame, this);
                }
            }
        }
        ++pos;
    }
    
    selectedSpawnRect.x += byX;
    selectedSpawnRect.y += byY;
}
